2014年7月アーカイブ

Android L Developer Preview、Material Design で追加された新ウィジェット(※1)である RecyclerView を使用してみました。

 

RecyclerView を使用すると、アイテムのサイズが固定長の場合に、従来よりも良いパフォーマンスが得られる(※2)ほか、

アイテムの追加、削除時などのアニメーション(※3)についても、デフォルトで利用することができます。

 

ちなみに RecyclerView のコードは、現時点(2014/7/11)では見れなかったのですが、前身のコード?っぽいのは、

sdk / sources / android-20 / android / support / v7 / widget / RecyclerView.java

にありました(約5,700行)。

(ソースコード読んで、notifyItemInserted() や notifyItemRemoved() の存在に気づくなど。。。)

 

以下、

従来の ListView & BaseAdapter で実装した場合のソースコードと、

RecyvlerView & RecyvlerView.Adapter で実装した場合のソースコードを貼っておきました。

文章で書くよりもコードを見た方が分かり易いと思いますので、コード内の番号をヒントに比較してみてください。

 

BaseAdapter(従来)のコード

public class SampleAdapter1 extends BaseAdapter {
    private LayoutInflater mLayoutInflater;
    private ArrayList<String> mDataList;

    // コンストラクタは、RecyclerView.Adapter でも変更なし
    public SampleAdapter1(Context context, ArrayList<String> dataList) {
        super();
        mLayoutInflater = LayoutInflater.from(context);
        mDataList = dataList;
    }

    // 4.RecyclerView.Adapter の場合、getItemCount() に書き換える
    @Override
    public int getCount() {
        return mDataList.size();
    }

    // RecyclerView.Adapter では不要
    @Override
    public Object getItem(int position) {
        return mDataList.get(position);
    }

    // RecyclerView.Adapter では不要
    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder viewHolder;
        if (convertView == null) {

            // 1.RecyclerView.Adapter の場合、この部分を onCreateViewHolder() で実装する
            // ViewHolder の引数には、インフレートしたビューを渡すように変更する
            convertView = mLayoutInflater.inflate(R.layout.list_item, parent, false);
            viewHolder = new ViewHolder();

            // 2.RecyclerView.Adapter の場合、この部分を ViewHolder のコンストラクタ内で実装する
            viewHolder.text = (TextView) convertView.findViewById(R.id.text);

            convertView.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) convertView.getTag();
        }

        // 3.RecyclerView.Adapter の場合、この部分を onBindViewHolder() で実装する
        String data = (String) getItem(position);
        viewHolder.text.setText(data);

        return convertView;
    }

    static class ViewHolder {
        TextView text;
    }
}

 

RecyvlerView.Adapter に書き換えた場合のコード

public class SampleAdapter2 extends RecyclerView.Adapter<SampleAdapter2.ViewHolder> {
    private LayoutInflater mLayoutInflater;
    private ArrayList<String> mDataList;

    public SampleAdapter2(Context context, ArrayList<String> dataList) {
        super();
        mLayoutInflater = LayoutInflater.from(context);
        mDataList = dataList;
    }

    @Override
    public SampleAdapter2.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        // 1
        View v = mLayoutInflater.inflate(R.layout.list_item, parent, false);
        ViewHolder viewHolder = new ViewHolder(v);
        return viewHolder;
    }

    // 4
    @Override
    public int getItemCount() {
        return mDataList.size();
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        // 3
        String data = (String) mDataList.get(position);
        holder.text.setText(data);
    }

    static class ViewHolder extends RecyclerView.ViewHolder {
        TextView text;

        public ViewHolder(View v) {
            super(v);
            // 2
            text = (TextView) v.findViewById(R.id.text);
        }
    }
}

 

ListView(従来)のコード

ListView listView = (ListView) view.findViewById(R.id.listview);

// アダプターの設定(mDataList は、ここでは ArrayList<String>)
listView.setAdapter(new SampleAdapter1(getActivity(), mDataList));

 

RecyvlerView に書き換えた場合のコード

RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.recyclerview);

// コンテキスト:Activity内ではthis、Fragment内ではgetActivity()など
recyclerView.setLayoutManager(new LinearLayoutManager(コンテキスト));
recyclerView.setHasFixedSize(true); // アイテムは固定サイズ

// アダプターの設定(mDataList は、ここでは ArrayList<String>)
recyclerView.setAdapter(new SampleAdapter2(getActivity(), mDataList));

 

ListView(従来)のリソース

<ListView
    android:id="@+id/listview"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

 

RecyvlerView に書き換えた場合のリソース

<android.support.v7.widget.RecyclerView
    android:id="@+id/recyclerview"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scrollbars="vertical" />

 

RecyvlerView.Adapter でデフォルトアニメーションを使用する例

// アイテム追加後、notifyItemInserted(index)を呼び出す(ここでは最後に追加)
recyclerView.getAdapter().notifyItemInserted(mDataList.size() - 1);

// アイテム削除後、notifyItemRemoved(index)を呼び出す(ここでは先頭を削除)
recyclerView.getAdapter().notifyItemRemoved(0);

 

備考

※1:N5だと、従来のListView と動作比較してみたところ、目に見えてわかるような差は体感できませんでした

※2:Material Design の追加ウィジェットは2つだけで、もう一方は以前紹介した CardView

※3:アニメーションは RecyclerView.ItemAnimator で自作したものへ置き換え可能

Android L(API Level 21)のViewAnimationUtils.createCircularRevealメソッドを使用すると、

ビューに対して広がる(/またはその逆の)円状のクリッピングを適用したアニメーション効果を得る事ができます(表現困難...)。

 

サンプル動画

※画面右端中央やや下のMapアイコンをタップしてビューを切り替えています

 

切り替え前後のビュー(参考)

before.png after.png

 

サンプルソースコード

// Revealアニメーションを適用したいビュー
final View animationTarget = findViewById(R.id.information_container);

int cx = (view.getLeft() + view.getRight()) / 2;
int cy = (view.getTop() + view.getBottom()) / 2;
float radius = Math.max(animationTarget.getWidth(), animationTarget.getHeight()) * 2.0f;

if (animationTarget.getVisibility() == View.INVISIBLE) {
    // 切り替えたいビューが非表示の場合(最初は、こっちを通る)

    // これから表示するビューを表示する(アニメーション開始時には円の半径が0なので実質見えない)
    animationTarget.setVisibility(View.VISIBLE);

    // 円状のクリッピングを適用したアニメーションを開始する
    // 円は(数百ミリ秒で)あっという間に大きくなります
    ViewAnimationUtils.createCircularReveal(
            animationTarget, // アニメーション対象のビュー(これから表示したいビュー)
            cx, cy, // アニメーションの原点(=円の中心)
            0,      // アニメーション開始時の円の半径
            radius  // アニメーション終了時の円の半径
    ).start(); // アニメーション開始
} else {
    // 切り替えたいビューが表示済の場合

    // 円状のクリッピングを適用したアニメーションを開始する
    // 円は(数百ミリ秒で)あっという間に小さくなります
    ValueAnimator reveal = ViewAnimationUtils.createCircularReveal(
            animationTarget, // アニメーション対象のビュー(これから表示したいビュー)
            cx, cy, // アニメーションの原点(=円の中心)
            radius, // アニメーション開始時の円の半径
            0       // アニメーション終了時の円の半径
    );
    // アニメーション終了時に呼び出されるリスナを設定
    reveal.addListener(new AnimatorListenerAdapter() {
        @Override
        public void onAnimationEnd(Animator animation) {
            // アニメーション終了時に対象のビューを非表示にする
            animationTarget.setVisibility(View.INVISIBLE);
        }
    });
    reveal.start(); // アニメーション開始
}

※デモアプリのソースコードは、https://github.com/romainguy/google-io-2014

 

Android L(API Level 21)のandroid.support.v7.graphics.Palette クラスを使用すると、指定した画像から、画像に使われている色を解析し、いい感じのパレット(6色分の色値)を計算してくれるようです。

取得した色をテキストやグレーアイコンに適用することで、画像にマッチした配色ができそうです(例えば、下図のフラミンゴだと、フラミンゴの色から、ピンク、赤、暗めの紺色の色などが Palette クラスから返される)。

 

Palette 呼び出し方

Palette palette = Palette.generate(photo); // photo は Bitmap

// get○○○Color()で任意のパレット色が取得できる

// VibrantColor
palette.getLightVibrantColor().getRgb();    // 明るい
palette.getVibrantColor().getRgb();
palette.getDarkVibrantColor().getRgb();     // 暗い

// MutedColor
palette.getLightMutedColor().getRgb();      // 明るい
palette.getMutedColor().getRgb();
palette.getDarkMutedColor().getRgb();       // 暗い

 

Google I/O 2014 Material Witness で紹介されていたデモアプリ

1_PaciflcaPier.PNG 2_PinkFlamingo.PNG

3_AntelopeCanyon.PNG 4_LonePine.PNG

※デモアプリのソースコードは、https://github.com/romainguy/google-io-2014

※キャプチャは、取得できるパレット色の名前と値を表示するように手を加えています

 

どんな風に色を選択しているか興味深く、Android L のソースコード公開が待ち遠しいです。

Android L Developer Preview で追加された UI 部品である CardView を試してみました。

 

動画

カードのふちが角丸(影つき)になっているのが見えますかね。。。

丸みは cardCornerRadius で指定するようです。

 

サンプルリソース

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"
    android:padding="8dp"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <android.support.v7.widget.CardView
        xmlns:card_view="http://schemas.android.com/apk/res-auto"
        android:layout_gravity="center"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        card_view:cardCornerRadius="8dp">

        <ImageView
            android:id="@+id/image"
            android:layout_width="match_parent"
            android:layout_height="280dp"
            android:scaleType="centerCrop" />

    </android.support.v7.widget.CardView>

</LinearLayout>

 

あれっ???

角が丸いだけの View ???

 

(そうだ、yanzm 先生の Android Pattern Cookbook に載ってたカードビューをみてみよう)

 

Android Pattern Cookbook に載ってたカードビューのリソース

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:paddingLeft="16dp"
    android:paddingRight="16dp"
    android:paddingTop="16dp" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/card_item_bg2"
        android:orientation="vertical" >

        <ImageView
            android:id="@+id/image"
            android:layout_width="match_parent"
            android:layout_height="@dimen/thumbnail_height"
            android:contentDescription="@string/thumbnail"
            android:scaleType="centerCrop" />

    </LinearLayout>

    <View
        android:layout_width="match_parent"
        android:layout_height="4dp"
        android:background="@drawable/card_shadow" />

</LinearLayout>

あれっ???

yanzm せんせいのとコード量、ほとんど変わらないw

CardView さん...

きっと、ボクが何か勘違いしているに違いない...

 

あわせて読みたい

1

2016年8月

  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31