20200929のAndroidに関する記事は6件です。

FlutterやってみたよPart7(retrofit導入)

初めに

ほとんどのアプリがapi通信をすると思います。
なんか楽にクライアント生成できるのないかな〜と調べていたらretrofitというライブラリを見つけました。
今回はそれを導入してみようと思います。

サンプルアプリ

まずはサンプルアプリの仕様をざっくり決めます。

  • Qiitaのapiを使用して最新記事を取得する
  • 取得した記事を一覧で表示する
  • 記事のタイトルをタップしたら記事詳細をwebviewで開く

今回はこんな感じの簡単なアプリにします!

retforitの仕組み

導入する前にざっくりとどういう仕組みで動くのか理解します。
公式のReadmeやSampleを見ればなんとなくわかると思いますが、
abstractでapiのエンドポイントを定義。
この定義されたファイルを元にクライアントの実体を自動生成する仕組みです。

自動生成されるファイルは.g.dartとgがつくのが慣例のようです。
(多分generateのgかな?)
生成されるファイル名はpart句で宣言します。

pubspec.yaml

お決まりのyaml定義。
※バージョンを固定する場合はanyを書き換えてください。

dependencies:
  http: any
  retrofit: ^1.3.4
  json_annotation: ^3.0.1

dev_dependencies:
  retrofit_generator: any
  json_serializable: any
  build_runner: any

apiクライアントのabstract

// qiita_client.dart

part 'qiita_client.g.dart';  // これが自動生成される実体のファイル名

// ここにbaseUrlを定義(引数で上書きできるようになってます)
@RestApi(baseUrl: "https://qiita.com/api")  
abstract class QiitaClient {
  // dioの説明は割愛しますm(_ _)m
  // ここはまだ実体(_QiitaClient)がないのでエラーになったままです。
  // 自動生成すると、qiita_client.g.dartの中に_QiitaClientができます
  factory QiitaClient(Dio dio, {String baseUrl}) = _QiitaClient; 

  @GET("/v2/items")
  Future<List<QiitaArticle>> fetchItems(
      @Field("page") int page,
      @Field("per_page") int perPage,
      @Field("query") String query);

}

リクエスト・レスポンスのデータクラス定義

今回はレスポンスだけ定義します。

// qiita_article.dart

part 'qiita_article.g.dart';

// クラスの中に独自クラスがあって展開する場合はexplicitToJson:trueにします。
// ここではQiitaUserという独自クラスがあるのでtrueにしてます。
@JsonSerializable(explicitToJson: true) 
class QiitaArticle {
  // JsonKeyでjsonの名前を定義します。同じなら省略できます。
  @JsonKey(name: 'rendered_body')
  String renderedBody;
  String body;
  bool coediting;
  @JsonKey(name: 'comments_count')
  int commentsCount;
  @JsonKey(name: 'created_at')
  DateTime createdAt;
  String group;
  String id;
  @JsonKey(name: 'likes_count')
  int likesCount;
  bool private;
  @JsonKey(name: 'reactions_count')
  int reactionsCount;
  List<QiitaTag> tags;
  String title;
  @JsonKey(name: 'updated_at')
  DateTime updatedAt;
  String url;
  QiitaUser user;
  @JsonKey(name: 'page_views_count')
  int pageViewsCount;

  QiitaArticle({
    this.renderedBody,
    this.body,
    this.coediting,
    this.commentsCount,
    this.createdAt,
    this.group,
    this.id,
    this.likesCount,
    this.private,
    this.reactionsCount,
    this.tags,
    this.title,
    this.updatedAt,
    this.url,
    this.user,
    this.pageViewsCount,
  });

}

自動生成を走らせる前は余計なコード(factryや定数とかゲッターとか)は書かないことをお勧めします。
何かしらのエラーが発生するとファイルが生成されなかったです。

自動生成

ファイルの準備が終わったらターミナルで以下コマンドを実行します。

flutter pub run build_runner build

正常に終了すると.g.dartがひょこっと出てきます。
image.png

マッピング関数追加

自動生成されたのでjson→クラス、factoryを追加してあげます。

part 'qiita_article.g.dart';

@JsonSerializable(explicitToJson: true)
class QiitaArticle {
  @JsonKey(name: 'rendered_body')
  String renderedBody;
  String body;
  bool coediting;
  @JsonKey(name: 'comments_count')
  int commentsCount;
  @JsonKey(name: 'created_at')
  DateTime createdAt;
  String group;
  String id;
  @JsonKey(name: 'likes_count')
  int likesCount;
  bool private;
  @JsonKey(name: 'reactions_count')
  int reactionsCount;
  List<QiitaTag> tags;
  String title;
  @JsonKey(name: 'updated_at')
  DateTime updatedAt;
  String url;
  QiitaUser user;
  @JsonKey(name: 'page_views_count')
  int pageViewsCount;

  QiitaArticle({
    this.renderedBody,
    this.body,
    this.coediting,
    this.commentsCount,
    this.createdAt,
    this.group,
    this.id,
    this.likesCount,
    this.private,
    this.reactionsCount,
    this.tags,
    this.title,
    this.updatedAt,
    this.url,
    this.user,
    this.pageViewsCount,
  });

  // ↓ 追記
  factory QiitaArticle.fromJson(Map<String, dynamic> json) => _$QiitaArticleFromJson(json);
  Map<String, dynamic> toJson() => _$QiitaArticleToJson(this);

  @override
  String toString() => json.encode(toJson());
  // ↑ 追記
}

apiクライアントを使う

できたクライアントを実際に使ってみます。
今回はクライアントを生成するリポジトリを作って呼び出すことにします。

Statusコードも欲しいのでApiResponseというクラスに変換して返却することにしました。
(これ本来はClient側でやるべきかもしれません)

class QiitaRepository {

  final QiitaClient _client;

  QiitaRepository([QiitaClient client]):
        // オプショナルの第2引数でbaseUrlを変更できる
        // QiitaClient(Dio(), "http://127.0.0.1:8081") という感じ
        _client = client ?? QiitaClient(Dio())  
  ;

  Future<ApiResponse> fetchArticle(int page, int perPage, String query) async {

    return await _client.fetchItems(page, perPage, query)
        .then((value) =>  ApiResponse(ApiResponseType.OK, value))
        .catchError((e) {
          // エラーハンドリングについてのretrofit公式ドキュメント
          // https://pub.dev/documentation/retrofit/latest/
          int errorCode = 0;
          String errorMessage = "";
          switch (e.runtimeType) {
            case DioError:
              // 失敗した応答のエラーコードとメッセージを取得するサンプル
              // ここでエラーコードのハンドリングると良さげ
              final res = (e as DioError).response;
              if (res != null) {
                errorCode = res.statusCode;
                errorMessage = res.statusMessage;
              }
              break;
            default:
          }
          // ・・・ 省略 ・・・
        });
  }

}

// 共通のレスポンスクラスとして定義
// resultはdynamicにしとく。(使う側でcastする)
class ApiResponse {

  final ApiResponseType apiStatus;
  final dynamic result;
  final String customMessage;

  ApiResponse(this.apiStatus, this.result, this.customMessage);

}

// ここは必要に応じて定義
enum ApiResponseType {
  OK,
  BadRequest,
  Forbidden,
  NotFound,
  MethodNotAllowed,
  Conflict,
  InternalServerError,
  Other,
}

呼び出してみる

今回はChangeNotifier使ってるのでViewModel側に呼び出し部分をコーディングしました。

class HomeScreenViewModel with ChangeNotifier {

  QiitaRepository _qiitaRepository;
  List<QiitaArticle> articles = [];

  HomeScreenViewModel([QiitaRepository qiitaRepository]) {
    _qiitaRepository = qiitaRepository ?? QiitaRepository();
  }

  Future<bool> fetchArticle() async {
    return _qiitaRepository.fetchArticle(1, 20, "qiita user:Qiita")
        .then((result) {
          if (result == null || result.apiStatus!= ApiResponseType.OK) {
            // TODO: 何かしらのエラー処理

            // 画面に変更通知
            notifyListeners();
            return false;
          }

          // 結果を配列にadd
          articles.addAll(result.result);
          // 画面に変更通知
          notifyListeners();
          return true;
        });
  }
}

画面側のリストはこんな感じで単純にリストに表示するようにしました。

// ・・・ 省略 ・・・
ListView.builder(
  key: Key(WidgetKey.KEY_HOME_LIST_VIEW),
  itemBuilder: (BuildContext context, int index) {

    var length = context.read<HomeScreenViewModel>().articles.length -1;

    // 最終行まできたら
    if (index == length) {
      // 追加読み込みの関数をcall
      context.read<HomeScreenViewModel>().loadMore(context);
      // 画面にはローディング表示しておく
      return new Center(
        child: new Container(
          margin: const EdgeInsets.only(top: 8.0),
          width: 32.0,
          height: 32.0,
          child: const CircularProgressIndicator(),
        ),
      );
    } else if (index > length) {
      // ローディング表示より先は無し
      return null;
    }

    // データがあるので行アイテムを作成して返却
    return Container(
      child: rowWidget(context, index),
      alignment: Alignment.bottomLeft,
      decoration: BoxDecoration(
          border: Border.all(color: Colors.grey)
      ),
    );
  },
)
// ・・・ 省略 ・・・

できました!
image.png

終わりに

面倒なapi通信の実体の部分を自動生成してくれるので結構楽チンでした。
baseUrlも差し替えられるのでモック化も問題無くできそうな気はします。(まだ試してないです)
使っていて問題があるようでしたら追記していこうと思います。

最終的なサンプルプロジェクトはこちら
※少しづつ手を加えてるのでこちらに記載したコードと違うところがあります。

次は単体テスト・ウィジェットテスト・結合テストについて調べてみようと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Android / Java】 フラグメントでの画面遷移と戻る処理

はじめに

Android Studio で Javaを用いたモバイルアプリの開発を学んでいます。
layoutファイルについてはあまり力を入れて記述していません(ざっくりです)。画面切り替えの動作等にフォーカスしているため、stringファイルは使用しませんでした。

学んだ内容

  • FragmentManager FragmentTransaction を用いた表示フラグメントの切り替え(画面遷移)
  • addToBackStack popBackStack を用いたアクションバー戻るボタンクリックで戻る動作

学習のために作成したサンプルアプリの概要

1つのアクティビティにメインとなるフラグメントを用意し、そこからボタンを押すことによって2つのフラグメントに行き来ができるだけの簡単なアプリ。
アクティビティ:1つ
フラグメント:3つ(メイン1つ、サブ2つ)

この①、②ボタンでサブフラグメント1、サブフラグメント2にそれぞれ遷移する

メイン画面(MainFragment)
サブ1画面(SubFragment1)
サブ2画面(SubFragment2)

ディレクトリ構成

スクリーンショット 2020-09-29 15.06.37.png

各javaファイルの処理内容

  • MainActivity.java

    • MainFragmentの表示
    • SubFragmentのアクションバーに戻るボタン「←」を表示するメソッドを定義
  • MainFragment.java

    • Fragmentの表示を切り替えるメソッドを定義
    • 設置したボタンのクリックリスナとFragmentを切り替えるメソッドの呼び出し
  • SubFragment(1, 2).java

    • アクションバーに戻るボタンの表示
    • 戻るボタンをクリックしたときに戻る処理を記述

ファイルのコードと解説

それぞれ、ファイルのコードを記載

layoutファイル

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activityMain"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

</FrameLayout>

fragment_main.xml
<?xml version="1.0" encoding="utf-8"?>

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/fragmentMain"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainFragment">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:textSize="25dp"
        android:text="メインフラグメントだよ"/>

    <Button
        android:id="@+id/bt1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="250dp"
        android:layout_marginLeft="70dp"
        android:textSize="25dp"
        android:text="①"/>

    <Button
        android:id="@+id/bt2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="250dp"
        android:layout_marginLeft="250dp"
        android:textSize="25dp"
        android:text="②"/>

</FrameLayout>

fragment_sub1.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/SubFragment1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".SubFragment1">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:textSize="25dp"
        android:text="サブフラグメント1だよ"/>

</FrameLayout>

fragment_sub2.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/SubFragment2"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".SubFragment2">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:textSize="25dp"
        android:text="サブフラグメント2だよ" />

</FrameLayout>

javaファイル

※package, import 文は記載していません。

MainActivity.java

MainActivity.java
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // メソッドを呼び出し、デフォルトでMainFragmentを表示
        addFragment(new MainFragment());
    }

    // Fragmentを表示させるメソッドを定義(表示したいFragmentを引数として渡す)
    private void addFragment(Fragment fragment) {
        // フラグメントマネージャーの取得
        FragmentManager manager = getSupportFragmentManager();
        // フラグメントトランザクションの開始
        FragmentTransaction transaction = manager.beginTransaction();
        // MainFragmentを追加
        transaction.add(R.id.activityMain, fragment);
        // フラグメントトランザクションのコミット。コミットすることでFragmentの状態が反映される
        transaction.commit();
    }

    // 戻るボタン「←」をアクションバー(上部バー)にセットするメソッドを定義
    public void setupBackButton(boolean enableBackButton) {
        // アクションバーを取得
        ActionBar actionBar = getSupportActionBar();
        // アクションバーに戻るボタン「←」をセット(引数が true: 表示、false: 非表示)
        actionBar.setDisplayHomeAsUpEnabled(enableBackButton);
    }
}

画面表示に指定したフラグメントを表示したいので transaction.add をする

引数はこのような値をいれる

transaction.add(追加先のレイアウト画面部品のR値, 追加する(表示したい)Fragmentオブジェクト);

MainFragment.java

MainFragment.java
public class MainFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // フラグメントで表示する画面をlayoutファイルからインフレートする
        View view = inflater.inflate(R.layout.fragment_main, container, false);

        // 所属している親アクティビティを取得
        MainActivity activity = (MainActivity) getActivity();
        // アクションバーにタイトルをセット
        activity.setTitle("メインフラグメント");
        // 戻るボタンは非表示にする(MainFragmentでは戻るボタン不要)
        // ここをfalseにしておかないとサブフラグメントから戻ってきた際に戻るボタンが表示されたままになってしまう
        activity.setupBackButton(false);

        // ボタン要素を取得
        Button bt1 = view.findViewById(R.id.bt1);
        Button bt2 = view.findViewById(R.id.bt2);

        // ①ボタンをクリックした時の処理
        bt1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // SubFragment1に遷移させる
                replaceFragment(new SubFragment1());
            }
        });

        // ②ボタンをクリックした時の処理
        bt2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // SubFragment2に遷移させる
                replaceFragment(new SubFragment2());
            }
        });

        return view;
    }

    // 表示させるFragmentを切り替えるメソッドを定義(表示したいFragmentを引数として渡す)
    private void replaceFragment(Fragment fragment) {
        // フラグメントマネージャーの取得
        FragmentManager manager = getFragmentManager(); // アクティビティではgetSupportFragmentManager()?
        // フラグメントトランザクションの開始
        FragmentTransaction transaction = manager.beginTransaction();
        // レイアウトをfragmentに置き換え(追加)
        transaction.replace(R.id.activityMain, fragment);
        // 置き換えのトランザクションをバックスタックに保存する
        transaction.addToBackStack(null);
        // フラグメントトランザクションをコミット
        transaction.commit();
    }
}

すでにメインフラグメントを表示しており、ボタンクリックで表示を切り替えたいので transaction.replace を使用
引数は add の処理と同じイメージ
transaction.replace(表示先のレイアウト画面部品のR値, 置き換える(表示したい)Fragmentオブジェクト);

メインフラグメントから遷移したサブフラグメントではアクションバーの戻るボタン「←」でメインフラグメントに戻れるようにしたいため
transaction.addToBackStack(null);
を記述することにより、遷移前に表示していたフラグメントを保存している。
この記述により遷移先のフラグメントで
getFragmentManager().popBackStack();
を呼び出すことで、戻るボタン「←」をクリックしたときに戻る処理ができるようになる

SubFragment1.java / SubFragment2.java

SubFragment1.java
public class SubFragment1 extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // フラグメントで表示する画面をlayoutファイルからインフレートする
        View view = inflater.inflate(R.layout.fragment_sub1, container, false);

        // 所属親アクティビティを取得
        MainActivity activity = (MainActivity) getActivity();
        // アクションバーにタイトルをセット
        activity.setTitle("サブフラグメント1");
        // 戻るボタンを表示する
        activity.setupBackButton(true);

        // この記述でフラグメントでアクションバーメニューが使えるようになる
        setHasOptionsMenu(true);

        return view;
    }

    // アクションバーボタンを押した時の処理
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {

        switch (item.getItemId()) {
            // android.R.id.homeで戻るボタン「←」を押した時の動作を検知
            case android.R.id.home:
                // 遷移前に表示していたFragmentに戻る処理を実行
                getFragmentManager().popBackStack();
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }
}

SubFragment2.java
public class SubFragment2 extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        // フラグメントで表示する画面をlayoutファイルからインフレートする
        View view = inflater.inflate(R.layout.fragment_sub2, container, false);

        // 所属親アクティビティを取得
        MainActivity activity = (MainActivity) getActivity();
        // アクションバーにタイトルをセット
        activity.setTitle("サブフラグメント2");
        // 戻るボタンを表示する
        activity.setupBackButton(true);

        // この記述でフラグメントでアクションバーメニューが使えるようになる
        setHasOptionsMenu(true);

        // View viewのが良い?
        return view;
    }

    // アクションバーのボタンを押した時の処理
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {

        switch (item.getItemId()) {
            // 戻るボタン「←」を押した時android.R.id.homeに値が入る
            case android.R.id.home:
                // 遷移前に表示していたFragmentに戻る処理を実行
                getFragmentManager().popBackStack();
                return true;
            default:
                return super.onOptionsItemSelected(item);
        }
    }
}

getFragmentManager().popBackStack();この記述で一つ前のフラグメントに戻る。

最後に

今回は内容に含みませんでしたが、今後フラグメント間のデータの受け渡しについても書けたら良いと思っています。

Java / Android を学び始めて1ヶ月弱程度の初心者です。ご指摘等ありましたらコメントください。

参考にさせていただいた資料

非常にわかりやすく助かりました!ありがとうございました!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

モバイルアプリにおけるUIデザイン

1.はじめに

私は(筆者は)モバイルエンジニア転職を目指す立場です。個人的にアプリのUIデザイン知識はデザイナーだけでなくエンジニアにも必須だと思っています。私自身がアプリ開発の過程でデザインについて指摘され、「このままデザインに無知では良くないと思った」、「同じような立場の方にモバイルアプリのデザインの事を少しでも知ってもらいたいと思った」その2つの理由から本記事を執筆しました。
現在、エンジニアを目指している方やアプリ開発はしているけどモバイルデザインについて改めて知りたいという方、そのような方向けに改めてiOS・Android双方のデザインガイドラインについてまとめてみました。

2.iOSとAndroidのデザインの考え方

2020年現在、iOSではHuman Interface Guidelines、AndroidではMaterial Designの考え方をもとにアプリUIが作られています。双方の公式ページはモバイルアプリを構成する UI の基本指針が細かく記されているのでエンジニア&デザイナー必読です。AppleよりGoogleの方が画像や映像を多く使って解説しているため、本記事もGoogle/Material Designの方が視覚的な解説が多いことをご容赦いただければ幸いです。HIGではMaterial Designほど、どう実装するかという細かいことは明記されておらず、ユーザーにとってUIがどうあるべきかを内容としています。一方、マテリアルデザインは各コンポーネントをどうするべきか、詳細に明記していることが内容に含まれています。
※あくまでもモバイルアプリでのUI前提の記事なので、本記事はWebやWindows(other OSを含む)のUIについては記載しません。
※本記事の情報は2020年9月現在の情報を元に執筆しています。

●ヒューマンインターフェースガイドライン(iOS)

まずはAppleのHuman Interface Guidelines(ヒューマン・インターフェース・ガイドライン、以下「HIG」)から。以下公式ページです。
https://developer.apple.com/design/human-interface-guidelines/ios/overview/themes/

iOS7以降、Appleのフラットデザイン1に基づいたデザイン指針がHPに記載されています。AppleはiPhone登場以来、スキューモフィズム2の代表と言えるような、現実世界に近づけたアイコンやUIを採用してきました。そこからiOS7で大きくデザインを変更しました。新たにHIGをアップデートし、フラットデザインを取り入れたガイドラインとして発表しました。平面的なデザインとそこにシャドウ(影)をつけたデザインで奥行きを再現し、可能領域を認識しやすくしています。

当時、AppleのiOS責任者であったジョナサン・アイブ氏はインタビューで

「人々がタッチスクリーンにすでに慣れて、十分使いこなしているため、物理的なボタンを模倣するようなことはもう必要ないと考えた。」

と語っています。
今日まで、そのHIGを元にiOSのアプリデザインは設計されています。

HIG1.png

Appleの目指すデザインには「3つの主要なテーマ」「6つのデザイン原則」、そして
主要な9つの項目」
があります。

○3つの主要なテーマ

1.Clarity(明快さ)

・テキストは様々なサイズで読みやすいテキスト
・正確で明快なアイコン
・装飾は巧妙で適切
・機能性を重視した動き
・余白、色、フォント、グラフィックス、インターフェイス要素は、重要なコンテンツを違和感なく強調し、ユーザーにインタラクティブ性があることを伝えられること

2.Deference([機能・コンテンツへの]敬意)

・滑らかな動きと美しいインターフェイスによって、ユーザーはコンテンツを理解し、操作を可能にする
・通常、コンテンツは画面全体に表示されるが、半透明性とぼやかし(ブラー)は、多くの場合でヒントになる
・ベゼル、グラデーション、ドロップシャドウの使用を最小限に抑えることで、インターフェイスを軽く風通しの良いものに保ちながら、コンテンツを最優先に(重要視する)にする

3.Depth(奥行き)

・はっきりした視覚的階層とリアルな動きが(コンテンツの)重なりを伝え、活気のある動きを与え、ユーザーへ理解しやすくする
・ユーザー体験は触れたり見つけやすくすることでより良いものとなり、文脈を見失うことなく機能や次のコンテンツに、アクセスすることができる
・トランジション(変化)はユーザーが奥行きを理解する助けとなる

○6つのデザイン原則

1.Aesthetic Integrity(美的完成度)

外観と中身の整合性は、アプリの見た目と動作が機能とどれだけうまく統合されているかを表しています。
例えば、ToDoアプリは、落ち着いて邪魔にならないグラフィック、標準化された予測できる操作でないといけない。一方、ゲームアプリは、楽しそうで魅力的な外観でないといけない。

2.Consistency(一貫性)

一貫性があるアプリは、システムが提供する馴染みのある規格、知られたアイコン、スタンダードなテキストスタイル、統一された用語、アプリの形や雰囲気を実装している。アプリにはその手法の特徴や挙動を取り入れる。

3.Direct Manipulation(直接操作)

画面上のコンテンツを直接操作することで、ユーザーを引き込み、何が起きているかを理解しやすくする。直接操作を行うことで、ユーザーは自分が取ったアクションの結果をすぐにはっきりと理解できるようになる。例えば、要素の並べ替えでは直接指でドラッグして移動できるようにする。

4.Feedback(フィードバック)

フィードバックはユーザーが取ったアクションを認識し、アクションによって起こった結果を伝える役割がある。内蔵されたiOSアプリはすべてのユーザーアクションに応答して、認識可能なフィードバックを提供します。
インタラクティブなUIはタップされたときにすぐにハイライト表示され、時間のかかる操作のステータスはプログレスバー3が表示される。アニメーションやサウンドはアクションの結果をはっきりと示すのに役立つ。

5.Metaphors(比喩の使用)

アプリの仮想オブジェクトとアクションが、現実世界とデジタル世界のどちらに根付いているかにかかわらず、慣れ親しんだ体験の比喩である場合、人々はより早く学習する。
人々は物理的に画面を操作するため、メタファー(現実世界に存在する物質や素材をUIデザインに落とし込むこと)はiOSでうまく機能する。それらはビューを邪魔にならないようにし、コンテンツを下に公開する。
スイッチを切り替えたり、スライダーを動かしたり、ピッカーの値をスクロールしたりすることである。それらは、本や雑誌のページもめくります。

6.User Control(ユーザーによる制御)

iOS全体では、アプリではなくユーザーがアプリをコントロールする。
アプリは一連のアクションを提案したり、危険である結果について警告したりするが、通常、アプリが意思決定を行うのは間違いである。良いアプリは、ユーザーのコントロール範囲を増やすことと、望まない結果を避けることの間でちょうど良いバランスを見つけます。
アプリは、インタラクティブな要素を使い慣れた予測可能な状態に保ち、(コンテンツが)削除されたことが確認でき、(処理中だとしても)操作を簡単にキャンセルできるようにすることで、ユーザーは自分たちがそのアプリをコントロールしている感覚を得る。

○3つの主要インターフェース

ほとんどのiOSアプリはプログラミングフレームワークであるUIKitのコンポーネントを使用して構築されています。
Apple(iOS)ではUIKitを元に、インターフェース要素を大きく「ナビゲーションバー」「ビュー」「コントロール」「3つ」に分けて考えます。今回は、その中から各要素の解説をいくつか例で紹介します。

1.ナビゲーションバー

スクリーンショット 0002-09-25 17.36.02.png
(出典:Human Interface Guidelines/Apple)

今、アプリ内のどこにいるのかをユーザーに伝え、ナビゲーションを提供します。アクションを開始したり、情報を伝達したりするためのボタンやその他の要素を配置することができます。

2.ビュー

テキスト、グラフィック、アニメーション、インタラクティブな要素など、アプリでユーザーが目にする主要なコンテンツを含みます。ビューは、スクロール、挿入、削除、配置などの動作を可能にします。
スクリーンショット 0002-09-25 18.08.23.png
(出典:Human Interface Guidelines/Apple)
上記画像はアクションシートです。
アクションシートを使用して、作業を開始したり、削除する操作を実行する前に確認を要求したりできます。

スクリーンショット 0002-09-25 22.58.54.png
(出典:Human Interface Guidelines/Apple)
上記画像はテーブルビューです。
左画像が基本(デフォルト)。行の左側にあるオプションの画像の後に、左揃えのタイトルが続きます。これは、補足情報を必要としないアイテムを表示するのに適したオプションです。右画像は字幕。1行に左揃えのタイトル、次の行に左揃えのサブタイトル。このスタイルは、行が視覚的に類似しているテーブルでうまく機能します。

3.コントロール

画面へのタッチによりアクションを開始し、情報を伝えます。ボタン、スイッチ、テキストフィールド、進行状況インジケーターは、コントロールの例です。

スクリーンショット 0002-09-25 18.11.28.png
(出典:Human Interface Guidelines/Apple)
上記画像は、システムボタンです。
ボタンタイトルには動詞を使用します。ボタンアクションのタイトルは、ボタンがインタラクティブであることを示し、タップすると何が起こるかを示しています。

スクリーンショット 0002-09-25 19.17.10.png
(出典:Human Interface Guidelines/Apple)
上記画像は、進捗インジケーターです。
アクティビティインジケーターを使用して、アプリが停止していないことをユーザーに知らせ、待機時間を知らせます。

○主要な9つの項目

HIGではユーザーにとって最善の体験価値を提供するため、アプリ設計、各動作、視覚的なデザイン、各コンポーネントがどうあるべきかが明記されています。解説内容がとても多い為、本記事では一部例のみ抜粋し紹介します。

1.アプリの設計
2.ユーザーインタラクション(相互作用)
3.システム機能
4.ビジュアルデザイン
5.アイコンと画像
6.バー
7.ビュー
8.コントロール
9.拡張機能

・アプリの設計

◎許可を求めることについて
ユーザーは、アプリが現在地、カレンダー、連絡先、リマインダー、写真などの個人情報にアクセスする場合、その事を許可しなければなりません。
スクリーンショット 0002-09-29 3.03.41.png
(出典:Human Interface Guidelines/Apple)

例えば、上の画像では現在の位置情報を取得して良いか許可を求めています。現在地の情報を反映させ、天気をより正確にアプリ内に表示させるため位置情報を利用するからです。
本文では「ユーザーは自分の物理的な位置情報を写真に自動的にタグ付けしたり、近くの友達を見つけたりすることができるのが好きですが、ユーザーは個人データをコントロールできるオプションがあることも望んでいます。」と書かれています。
「個人データを要求するのは、アプリが明らかに必要としている場合に限ります。」と書かれているように、プライバシー保護の観点から許可を求める際のガイドラインが書かれています。許可を求める場合にはアプリに情報が必要な理由をアラート内で説明して下さいと明記されています。

・ビジュアルデザイン

◎色について

色は、活力を与え、視覚的な連続性を提供し、ステータス情報を伝え、ユーザーのアクションに応じてフィードバックを与え、データを視覚化するのに最適な方法であることが説明されています。明るい背景と暗い背景の両方で、組み合わせて見栄えの良いアプリの色合いを選択します。下記URL内の各項目で、色についてどこに気をつければよいのかが書かれています。
https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/color/

・コミュニケーションのための色は慎重に
・アプリ全体で補色を使用
・一般的には、アプリのロゴとコーディネートする限定的なカラーパレットを選択する
・アプリ全体のインタラクティブ性を示す色合いを選択することを検討する
・明るいモードと暗いモードの両方で見栄えが良いことを確認するために、2つのバージョンの色合いを用意する
・インタラクティブ要素と非インタラクティブ要素に同じ色を使用しない
・アートワークと半透明度が近隣の色にどのように影響するかを考慮する
・さまざまな照明条件でアプリの配色をテストする
・True Tone ディスプレイがカラーにどのように影響するかを検討する
・色の使用が他の国や文化でどのように認識されるかを考慮する
・アプリのコンテンツを認識しづらい色を使用しないようにする

・システム機能

◎通知
アプリは通知を使用して、デバイスがロック中であっても使用中であっても、いつでもタイムリーに重要な情報を提供することができます。例えば、通知はメッセージが到着したとき、イベントが発生しようとしているとき、新しいデータが利用可能になったとき、または何かのステータスが変更されたときに通知することができます。人々は、ロック画面、デバイスを使用中の画面上部、通知センターで通知を見ることができます。
スクリーンショット 0002-09-29 3.50.05.png
(出典:Human Interface Guidelines/Apple)

上の画像ような通知はローカルでもリモートでも表示できるようになっています。例えば、Todoリストアプリでは、ローカル通知を使用して、予定されている会議や期日について通知(警告)することができます。
リモート通知は、プッシュ通知とも呼ばれています。これはサーバーから送られてきます。例えば、マルチプレイヤーゲームでは、自分の出番が来たときにリモート通知を使用してプレイヤーに知らせることがあります。
また、優れた通知体験を提供するために以下の様な注意事項が明記されています。

・簡潔で読みやすい通知を作成し、有益な情報を提供する
・通知には、機密情報、個人情報、機密情報を含めないようにする
・ユーザーが応答していない場合でも、同じ内容の通知を複数回送信しないようにする
・通知のプレビューが非表示になっているときに表示されるように、説明的で非特定のテキストを提供する
・アプリ名やアイコンを含めない
・通知を補足するためにサウンドを提供することを検討する
・詳細ビューの提供を検討する
・直感的で有益なアクションを提供する
・破壊的なアクションを提供しないようにする

○ユーザインターフェイスのデザインのヒント

これはAppleが公式で日本語ページにて公開しているものなので、下記URLからチェックしてみて下さい。
https://developer.apple.com/jp/design/tips/#clarity

本記事では一部抜粋して紹介します。

・タッチコントロール

スクリーンショット 0002-09-26 1.11.59.png
(出典:ユーザインターフェイスのデザインのヒント/Apple

タッチコントロールはガイドライン内、コントロール-ボタンを参照しています。
右の✖画像はカレンダーの表示が小さく、どこをタッチすればよいか分からなくなってしまっています。左の✔画像の様に、アプリ内で簡単かつ自然に操作できるようにしなければなりません。

・文字の大きさ

スクリーンショット 0002-09-26 2.09.12.png
(出典:ユーザインターフェイスのデザインのヒント/Apple

文字の大きさはガイドライン内、ビジュアルデザイン-タイポグラフィを参照しています。
右の✖画像は表示されている文字が小さく、読むことが困難です。最低でも文字は11ポイント以上のサイズに設定し、ズームしなくても読めるようにしなければなりません。

通常の閲覧距離でもズームすることなく快適に読めるようにしなければなりません。また、フォント色とビューの背景色とのコントラストが悪い場合や、文字の間隔が狭い場合は可読性が失われるため、ガイドラインを参考に適切に設定しなければなりません。

●マテリアルデザイン(Android)

次に、2014年にGoogleが発表したマテリアルデザインです。以下、公式サイトです。
ガイドラインについてはサイト内、Material Guidelinesを御覧ください。
https://material.io/

スクリーンショット 0002-09-24 14.46.41.png

マテリアルデザインは平面的な質感をもたせつつ、物質の重なりを意識させるためにシャドウを落として奥行きを強く感じさせるものになっています。「紙」と「インク」の要素でできているという考えを持っており、2014年の発表時から今日まで変わりません。
目的は大きく分けて2つあり、1つ目は「どんなデバイスでも共通化されたデザインを実現するため」、2つ目は「ユーザーに"直感的"な操作体系を提供するため」です。以上の2つを元に、マテリアルデザインは自然の世界を物理法則に従い、メタファー(隠喩)としてデジタルスクリーンに落とし込んでいます。また、コンテンツを多様な画面サイズで、変わりなく表示できるようデザインすることも、マテリアルデザインの目的としてあるようです。

上記のガイドラインに則って開発されているコンポーネント(ボタンやビュー)がマテリアルコンポーネントです。この基礎的なコンポーネントを最初から用意しておくことで、「車輪の再開発を止め、開発者やデザイナーがプロダクトのサービスやブランドの体験作りの方に注力できるようにして欲しい」というGoogle側の意図があります。

マテリアルデザイン(2014〜)

Googleの目指すデザインには当初「4つのデザイン原則」がありました。現在はアップデートされ3つに統一されています。しかし、現在のマテリアルデザインの元となっているものなので初期のデザイン原則も紹介しておきます。
マテリアル4つのデザイン原則.jpg

○4つのデザイン原則(2014年)

1.タンジブルサーフェス

RPReplay_Final1601012338-2.gif
(出典:Material Design/Google Developers)

マテリアルデザインでは画面は小さな四角い紙(英語ではサーフェス)によってできていると考えます。画面内に紙のような物があって、その上に何かが乗っているというような考えです。その特徴を上げると以下のようなことが分かります。

・紙と紙が離れる
・紙と紙がくっつく
・紙を動かす事が出来る
・高さの違う紙が重なっていると、その下の紙には影が出来る
ユーザーは自然に「上のものをどけたら下には何かがあるだろう」という発想になり、説明なくコンテンツを理解してもらえるようになっています。

ガイドラインページ:

マテリアルデザイン:Surfaces

2.印刷物のようなデザイン

マテリアルデザイン1.png
(出典:Material Design)

印刷物のデザインにはキーラインというものがあります。どこに揃えて文字を表示するかを考え、縦に引いたガイドラインに合わせてテキストや画像を配置するだけで、デザインが美しく見えるようになります。それをデジタルスクリーン上で再現することによって、より美しく、より見やすくすることができます。

3.意味があるアニメーション

マテリアルデザイン2.gif
(出典:Material Design)
マテリアルデザインではアニメーションを使いますが、それは意味のあるアニメーションであるべきと考えます。どこから来て、どこへ行ったのかを視覚的に見せることで、自分が今何をしているのかということが理解しやすくなります。

4.アダプティブデザイン

マテリアルデザイン3.png
(出典:Material Design/Google Developers)

今日、私達は5インチの小さな画面から、10インチを超える大きな画面サイズでコンテンツを操作します。画面サイズが変わっても、裏で使われているアプリやシステムは同じで、表示サイズによって画面サイズを適切な見え方にしようという考えです。

〜現在のマテリアルデザイン〜

ここから現在のマテリアルデザインについて説明します。現在、マテリアルデザインは「Material System(マテリアルシステム)」、「Material Foundation(マテリアルファンデーション)」、「Material Guidelines(マテリアルガイドライン)」の3つに分けて説明されています。1つ目のマテリアルシステムではデザイン3原則について解説されています。2つ目のマテリアルファンデーションでは、基礎となるグリッドやカラーパレット、コンポーネント全般について解説されています。3つ目のマテリアルガイドラインではMaterial Theming(マテリアルシーミング)とアクセシビリティ、Android向けガイダンスについて記載されています。

○3つの原則

Material Design - Introduction
ここではマテリアルデザインの3つの原則について明記されています。

1.材料はメタファーである

マテリアル4-1.png

マテリアルデザインは、物理的な世界とそのテクスチャ(特徴)から発想を得ています。これには、光を反射したり影を落としたりする方法も含まれます。各素材の表面は、紙とインクの媒体を再考します。

2.太字、グラフィック、意図的

マテリアル4-2.png

マテリアルデザインは、印刷デザイン手法(タイポグラフィ、グリッド、スペース、スケール、色、イメージ)を用いて、階層、意味、および焦点を形成し、視聴者を体験に没頭させます。

3.モーション(動き)は意味を与えます

マテリアル4-3.png

モーションは注意を集中させ、微妙なフィードバックと一貫した移行を通じて継続性を維持します。要素が画面に表示されると、インタラクションによって環境を変形させ、再編成し、新たな変形を生み出します。

○マテリアルファンデーション

ここでは基礎となるレイアウト、グリッド、カラー、アイコン、モーションなどを大きく12項目に分けてどうすれば良いかが明記されています。HIG同様、解説内容がとても多い為、本記事では一部例のみ抜粋し紹介します。
詳細は、Material Design - Foundationでご確認下さい。

1.環境(Environment)
2.レイアウト(Layout)
3.ナビゲーション(Navigation)
4.色(Color)
5.タイポグラフィ(Typography)
6.音(Sound)
7.アイコン(Icons)
8.形状(Shape)
9.動き(Motion)
10.相互作用(Interaction)
11.コミュニケーション(Confirmation)
12.機械学習(Machine learning)

・環境(Environment)

https://material.io/design/environment/surfaces.html#material-environment

◎面、奥行き、影について

要素は、水平方向、垂直方向、およびz軸に沿ってさまざまな深さで移動します。ここでは下の図を用いることで奥行きをZ軸で説明し、UIの操作はY軸であることを説明しています。
マテリアル5.png

・レイアウト(Layout)

https://material.io/design/layout/understanding-layout.html#
ここではレイアウトグリッドについて、間隔をどう開けたらよいかについてなどが説明されてます。
◎材料の測定
マテリアルデザインのレイアウトが視覚的にバランスが取れているのは8dpグリッドを基準として、その倍数に数値を合わせて設定していることが説明されています。アイコンなどの小さなコンポーネントは4dpに揃えることで、バランスを保ったレイアウトが調整されています。
マテリアル6.png

・ナビゲーション(Navigation)

ユーザーはアプリ内を移動します。その際の導線やタスク、動きなどの解説がされています。

◎ナビゲーションの方向(パターン)
ナビゲーション方向パターンは横方向のナビゲーション、フォワードナビゲーション、リバースナビゲーションの3種類に分けて説明し、音楽アプリの構造を例に説明されています。
本記事では横方向のナビゲーションを取り上げます。横方向のナビゲーションは、名前の通り横に展開していく構成で、同じ階層レベルの画面間を移動するナビゲーションのことです。アプリの主要なナビゲーションコンポーネントは、階層(レイヤー)の最上位にあるすべての動作先へのアクセスを提供する必要があります。
下の図ではアプリ構造のトップの階層にある、「Library」,「Recently Played」、「Search」の画面間を移動できます。
マテリアル7-1.png

スクリーンショット 0002-09-27 17.03.10.png
(出典:MaterialDesign - Understanding navigation)
(図表"公式ページより翻訳" by Masaki Sugita)

1.ナビゲーションドロワー

ナビゲーションドロワーは、5つ以上のトップレベルの目的地に適しており、一貫したナビゲーション体験のためにデバイスのサイズを超えて使用することができます
マテリアル7-2.png

2.下部ナビゲーションバー

下部のナビゲーションバーは、モバイルデバイス上でトップレベルの 3~5 の遷移先へのアクセスを提供します。これらのバーの位置、可視性、画面上での持続性により、遷移間のピボット(方向転換)を素早く行うことができます。
マテリアル7-3.png

3.タブ

タブは、アプリの階層のどのレベルでも使用でき、画面サイズをまたいで2つ以上のデータのピアセットを表示することができます。
マテリアル7-4.png

○マテリアルガイドライン

マテリアルガイドラインでは「マテリアルシーミング」「使いやすさ」「プラットフォームガイダンス」3つに分けて説明されています。2018年のガイドラインアップデートで登場したのがマテリアルシーミングです。
マテリアルデザインを採用すると、どのプロダクトも共通のデザイン(UI)になってしまい、似たりよったりのデザインが出来上がってしまうという問題がありました。その問題を解決したのが、マテリアルシーミングです。マテリアルシーミングを利用すると、プロダクトをカスタマイズしてコンポーネントで開発するプロセスが簡単になり、サービスを使いやすく、機能的にするためにコンポーネントを使用できます。コンポーネントは、各開発プロダクトに使用可能で機能的にするための構成要素です。

1.マテリアルシーミング

マテリアルシーミング は、プロダクトのブランドをより良く反映させるためにマテリアルデザインを体系的にカスタマイズしブランドに合わせて拡張できる機能です。UIの側面を変更すると、プロダクトのブランドをよりよく反映させることができます。アプリのすべてのモジュールのところまでカスタマイズ可能にしておいて、かつ全体のテーマ感を損なわないようになっています。

マテリアル8.gif
(出典:Material Design - Material Theming)

例えばボタンの例です。
ボタンを各プロダクトのブランド向けにカスタマイズしたい場合はマテリアルシーミングは、ボタンのような個々のコンポーネントを含む UI 全体に影響を与えます。この例では、ボタン コンポーネントの既成概念にとらわれない値をどのようにカスタマイズできるかを示しています。アプリ内でボタンの色や角Rをカスタマイズし、各画面に適用させるためにマテリアルシーミングを使用します。

スクリーンショット 0002-09-28 15.05.55.png

上の図では左側がデフォルトのボタンで、右側がカスタマイズされたボタンです。下記は左側のデフォルト設定。
・色は#6200EE
・テキストは14pt、Roboto、Mediumで全て大文字
・ボタンの角は4dpの丸い半径
このデフォルト設定から右画像のボタンに仕上げるまで、タイポグラフィー、色、図像(アイコン)、形状をどのように設定するかが記載されています。
https://material.io/design/material-theming/overview.html#using-material-theming

2.使いやすさ(アクセシビリティ)

デザインにおけるアクセシビリティは、多様な能力を持つユーザーがUIをナビゲートし、理解し、使用することを可能にします。ここでは、低視力、失明、聴覚障害、認知障害、運動障害、または状況障害(腕の骨折など)を含むすべてのユーザーの使いやすさについて記載されています。
例えばスクリーンリーダーです。

・スクリーンリーダー
視覚障害、読書困難、または一時的に文字が読めない人は、スクリーンリーダーを使用することがあります。スクリーン・リーダーは点字ディスプレイか、声を出しテキストを読み込んでくれるソフトウェアプログラムのトークバックを使用します。
スクリーンリーダーは、表示されているコンテンツを言語化して読み上げます。段落とボタンのテキスト、およびアイコンと見出しの代替テキストなどの非表示のコンテンツは、プログラムによって識別されます。コンテンツによってこの機能を使い分け、スクリーンリーダーを使用するユーザーエクスペリエンスを最適化できます。

その他、アクセシビリティからの視点で階層や色、タイポグラフィーに関して明記されていますので下記サイトから確認してみて下さい。
https://material.io/design/usability/accessibility.html#assistive-technology

3.プラットフォームガイダンス

ここでは、Androidアプリ内のナビゲーションバー、ハプティクス(接触)フィードバック、Android内の通知についてなどを、どのように実装すれば良いのかのガイドラインが14項目に分けて、明記されています。
https://material.io/design/platform-guidance/android-bars.html#status-bar
・バー
・指紋
・ハプティクス(接触)
・アイコン
・アプリ間の移動
・通知
・権限
・設定
・スライス
・分割画面
・スワイプして更新
・テキスト選択ツールバー
・ウィジェット
・クロスプラットホーム

3.まとめ

今回調査してみて、Appleは抽象的、Googleは具合的に書かれているということが分かりました。
共通点は、「使うユーザーが異なる画面サイズで操作しても、どんなユーザーが操作しても、同じユーザーエクスペリエンス(UX)ができるように設計することが大切だ」という考えを持っていることでした。OSによって根本的な考えが異なるとはいえ、世界のモバイルOSを牽引している2社のデザインガイドラインはユーザーが操作しやすく、プロダクトやブランドをどうしたらよく見せられるかということがしっかり明記されていました。本記事では解説が不十分なところがまだまだ多くあるので、今後調査しながらさらに情報をアップデートしていこうと思います。
皆様の開発に少しでもお役に立てれば幸いです。

4.参考記事/動画/書籍 一覧

Apple - Human Interface Guidelines
Google - Material Design

-HIG
ユーザインターフェイスのデザインのヒント
Apple ヒューマンインターフェースガイドライン輪読のすゝめ
iOS ヒューマンインターフェースの原則
「深津貴之氏に学ぶ、スマホUI/UX講座 〜iOS7についての考察とfladdictデザイン論〜」に参加してきました。
今さら人には聞けない、スキューモーフィズムとフラットデザインの違い
独学でUIデザインはじめた方へ。デザインガイドラインについて語ろう!
Macintosh から iPhone へ受け継がれるデザイン原則
常にキャッチアップしよう!iOSヒューマンインターフェイスガイドライン
2. マッキントッシュ, 3. ヒューマンインターフェイス ガイドライン [2020.6.2]

-Material Design
マテリアルデザインについて少し調べる
Material Designの設計思想を探る
I/O 2014 アプリに学ぶマテリアルデザイン
Android アプリにマテリアル デザインを導入する
マテリアルデザインに見る機能的なアニメーションの6つの法則
サンクスブログ Googleが発表したマテリアルデザイン
マテリアルデザインでよりよいユーザー体験を実現しよう
マテリアルデザインとは何か?- 最新Webデザイントレンド
【マテリアルデザイン採用】リニューアルで気をつけた4つの大事なポイント -feedly編-
Google, Apple, Audi ── デザインシステムのメニューを見比べれば、企業とデザインの関係がわかる
Material Theming 概要
12 Absolute Principles of Material Design

-動画
Material Theming - Material Design の先へ(Youtube)
DroidKaigi 2019 - マテリアルデザインの起源とベースとなる哲学 / ken(Youtube)

-書籍
フラットデザインで考える 新しいUIデザインのセオリー
UI GRAPHICS 成功事例と思想から学ぶ、これからのインターフェイスデザインとUX


  1. フラットデザインとは、できるだけ影やグラデーションなどの装飾を使わずに表現したシンプルで平面的なデザインのこと。Apple自体はフラットデザインというワードは使っていない。 

  2. 他の物質(現実世界の物)に似せるために行うデザインや装飾のこと。 ユーザーに馴染みのないものや初めて触るものを、現実世界の物質(物体)のようにデジタル上で再現することで、ユーザーに理解してもらい易くすくする手法。 

  3. 長時間かかるタスクの進捗状況がどの程度完了したのかを視覚的・直感的に表示するもの。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UnityChanSSUをAndroid実機ビルドした際シェーダのコンパイルエラーで失敗した時の対処法

個人的に偶然キレイなビジュアルが作れたので、PCではなくスマホで動かしたくなりました。

Androidビルドした所、UniversalToonシェーダのコンパイルエラーで失敗。

Shader error in 'Universal Render Pipeline/Toon': invalid subscript 'vertexSH' at /Unityプロジェクトまでのパス/Packages/UnityChanToonShaderVer2_Project-release-urp-2.2/Runtime/Shaders/UniversalToonBodyDoubleShadeWithFeather.hlsl(41) (on gles3)

上記のようなエラーが出力されていました。

シェーダの確認

エラー内容に従ってシェーダを調査します。


UniversalToonを選択してインスペクタを確認。

おおお、、エラー沢山。。。
とりあえずinvalid subscript 'vertexSH'が原因ぽい。

シェーダのReimportで解決

UniversalToonを右クリックからReimport実行。

なぜかと言われると答えられませんが、UniversalToonシェーダをReimportすると直ります。

するとこのようにきれいなインスペクタに戻ります。

ビルドは成功しませんでした

インスペクタ上ではコンパイルは成功しているのですが、実機ビルドでは失敗します。また、再度シェーダのインスペクタを見てみるとエラーは元に戻っていました。

OpenGLES3.1をターゲットにする

Require ES3.1にチェックを入れることでビルドが成功しました。
詳しく調べられてはいませんが、ES3.0以下では対応していないシェーダがあったということだと思われます。

無事に動いた!!

無事にUnityChanSSUがAndroid実機で動くようになりました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Android] BottomSheetsを使ってみた [メモ]

はじめに


BottomSheetとは画面の下から生えてくるやつなんですけど、今回はこれを実装してみようと思います。上記の画像はMaterial Designから抜粋してきました。

Gradle

何をやるにもまずはGradleに依存ライブラリを記述します。

implementation 'com.google.android.material:material:1.2.1'

実装

レイアウトは、BottomSheet用のレイアウトを別で作成し、includeタグを用いてCoordinatorLayoutにレイアウトを埋め込みます。

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#e7e7e7"
    tools:context=".MainActivity"
    >

    <include layout="@layout/bottom_sheet" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>
bottom_sheet.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    android:id="@+id/bottomSheet"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#FFFFFF"
    android:orientation="vertical"
    app:behavior_hideable="true"
    app:behavior_peekHeight="16dp"
    app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
    >

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:drawablePadding="24dp"
        android:paddingEnd="0dp"
        android:paddingStart="20dp"
        android:paddingVertical="12dp"
        android:text="@string/share"
        android:textColor="@color/black"
        android:textSize="16sp"
        app:drawableStartCompat="@drawable/ic_baseline_share_24"
        />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:drawablePadding="24dp"
        android:paddingEnd="0dp"
        android:paddingStart="20dp"
        android:paddingVertical="12dp"
        android:text="@string/get_link"
        android:textColor="@color/black"
        android:textSize="16sp"
        app:drawableStartCompat="@drawable/ic_baseline_link_24"
        />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:drawablePadding="24dp"
        android:paddingEnd="0dp"
        android:paddingStart="20dp"
        android:paddingVertical="12dp"
        android:text="@string/edit"
        android:textColor="@color/black"
        android:textSize="16sp"
        app:drawableStartCompat="@drawable/ic_baseline_edit_24"
        />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:drawablePadding="24dp"
        android:paddingEnd="0dp"
        android:paddingStart="20dp"
        android:paddingVertical="12dp"
        android:text="@string/delete"
        android:textColor="@color/black"
        android:textSize="16sp"
        app:drawableStartCompat="@drawable/ic_baseline_delete_24"
        />
</LinearLayout>

BottomSheetを実装するには、CoordinatorLayoutレイアウトの配下に、app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"を指定したレイアウトを配置する必要があります。他のアトリビュートは、
- behavior_hideable: BottomSheetを画面の最下部に移動した時にBottomSheetを完全に隠すか否かを指定できる
- behavior_peekHeight: 表示されるBottonSheetの高さ
です。
無事実装できました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Android] プロフィール画像とかでよく見る画像を丸にくり抜くやり方 [メモ]

はじめに

プロフィール画像とかでよく見る、画像を円型に表示させる方法。色々なやり方があると思うが、今回はMaterial ComponentのShapableImageViewを使います。

目標

Gradle

implementation 'com.google.android.material:material:1.2.1'

実装

shapeAppearanceOverlay属性に任意のスタイルを当てはめると簡単に円型やひし形などカスタマイズできます。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:background="@color/black"
    >
    <!--   円 -->
    <com.google.android.material.imageview.ShapeableImageView
        android:id="@+id/circle"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:scaleType="centerCrop"
        android:layout_marginTop="80dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintEnd_toStartOf="@id/rhombus"
        app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.App.Circle"
        app:srcCompat="@drawable/test"
        />
    <!--   ひし形 -->
    <com.google.android.material.imageview.ShapeableImageView
        android:id="@+id/rhombus"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:scaleType="centerCrop"
        android:layout_marginTop="80dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@id/circle"
        app:layout_constraintTop_toTopOf="parent"
        app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.App.Rhombus"
        app:srcCompat="@drawable/test"
        />

</androidx.constraintlayout.widget.ConstraintLayout>
    <style name="ShapeAppearanceOverlay.App.Circle" parent="" >
        <item name="cornerSize">50%</item>
    </style>

    <style name="ShapeAppearanceOverlay.App.Rhombus" parent="" >
        <item name="cornerSize">50%</item>
        <item name="cornerFamily">cut</item>
    </style>

他にも様々なカスタマイズができるようです。参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む