17

バックグラウンド

Google は最近、次のような連絡先アプリを備えた新しいバージョンの Android を公開しました。

ここに画像の説明を入力

問題

この種のリストを模倣する必要がありますが、うまく行う方法がわかりません。

これは、次の 3 つの主要コンポーネントで構成されています。

  1. 最上位の連絡先の名前がこの文字で始まる限り、スティッキー タイトルが付けられます。

  2. 円形の写真または円形 + 文字 (写真がない連絡先の場合)

  3. PagerTitleStrip は、項目間の間隔が均一で、すべての項目を画面に表示しようとします (または、必要に応じてスクロールできるようにします)。

私が見つけたもの

  1. サードパーティのライブラリはたくさんありますが、それらは listView 自体の項目の一部であるスティッキー ヘッダーを処理します。左側にはタイトルがありませんが、アイテムの上部にあります。

    ここで、左側は右側とは異なる方法で動きます。貼り付けることができ、貼り付ける必要がない場合 (セクションに項目が 1 つある場合など) はスクロールします。

  2. 円形の写真の場合、「 RoundedBitmapDrawableFactory 」という新しい (?) クラスを使用できることに気付きました (「RoundedBitmapDrawable」を作成します)。これで、写真をきれいに円形にすることができそうです。ただし、文字(写真のない連絡先に使用)では機能しませんが、textViewを上に置き、背景を色に設定できると思います。

    また、「RoundedBitmapDrawable」をうまく使用するには (真に円形にするために)、正方形サイズのビットマップを提供する必要があることに気付きました。そうしないと、奇妙な形になります。

  3. setTextSpacing」を使用してアイテム間のスペースを最小化しようとしましたが、うまくいかないようです。また、連絡先アプリのように PagerTitleStrip をスタイル/カスタマイズする方法も見つかりませんでした。

    PagerTabStrip」も使用してみましたが、これも役に立ちませんでした。

質問

Google がこの画面を実装した方法をどのように模倣できますか?

すなわち:

  1. 左側を連絡先アプリのように動作させるにはどうすればよいですか?

  2. これは円形の写真を実装する最良の方法ですか? 特別な drawable を使用する前に、ビットマップを正方形にトリミングする必要がありますか? どの色を使用するかについてのデザイン ガイドラインはありますか? 円テキストセルを使用するより公式な方法はありますか?

  3. PagerTitleStrip のスタイルを連絡先アプリと同じルック アンド フィールにするにはどうすればよいですか?


Github プロジェクト

編集: #1 と #2 については、Github でプロジェクトを作成しまし。悲しいことに、重要なバグも発見したので、ここにも公開しました。

4

5 に答える 5

7

わかりました、私が書いたすべての問題を解決することができました:

1.サードパーティのライブラリの動作方法を変更しました(ライブラリをどこから入手したか覚えていませんが、これは非常に似ています)、各行のレイアウトを変更して、ヘッダーが左側になるようにしましたコンテンツ自体の。レイアウト XML ファイルの問題だけで、ほぼ完了です。おそらく、これらのソリューションの両方に適したライブラリを公開する予定です。

2.これは私が立てた見解です。これは公式の実装ではない (何も見つからなかった) ため、自分で何かを作成しました。より効率的になる可能性がありますが、少なくとも非常に理解しやすく、非常に柔軟です。

public class CircularView extends ViewSwitcher {
    private ImageView mImageView;
    private TextView mTextView;
    private Bitmap mBitmap;
    private CharSequence mText;
    private int mBackgroundColor = 0;
    private int mImageResId = 0;

    public CircularView(final Context context) {
        this(context, null);
    }

    public CircularView(final Context context, final AttributeSet attrs) {
        super(context, attrs);
        addView(mImageView = new ImageView(context), new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
                LayoutParams.MATCH_PARENT, Gravity.CENTER));
        addView(mTextView = new TextView(context), new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
                LayoutParams.MATCH_PARENT, Gravity.CENTER));
        mTextView.setGravity(Gravity.CENTER);
        if (isInEditMode())
            setTextAndBackgroundColor("", 0xFFff0000);
    }

    @Override
    protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        final int measuredWidth = getMeasuredWidth();
        final int measuredHeight = getMeasuredHeight();
        if (measuredWidth != 0 && measuredHeight != 0)
            drawContent(measuredWidth, measuredHeight);
    }

    @SuppressWarnings("deprecation")
    private void drawContent(final int measuredWidth, final int measuredHeight) {
        ShapeDrawable roundedBackgroundDrawable = null;
        if (mBackgroundColor != 0) {
            roundedBackgroundDrawable = new ShapeDrawable(new OvalShape());
            roundedBackgroundDrawable.getPaint().setColor(mBackgroundColor);
            roundedBackgroundDrawable.setIntrinsicHeight(measuredHeight);
            roundedBackgroundDrawable.setIntrinsicWidth(measuredWidth);
            roundedBackgroundDrawable.setBounds(new Rect(0, 0, measuredWidth, measuredHeight));
        }
        if (mImageResId != 0) {
            mImageView.setBackgroundDrawable(roundedBackgroundDrawable);
            mImageView.setImageResource(mImageResId);
            mImageView.setScaleType(ScaleType.CENTER_INSIDE);
        } else if (mText != null) {
            mTextView.setText(mText);
            mTextView.setBackgroundDrawable(roundedBackgroundDrawable);
            // mTextView.setPadding(0, measuredHeight / 4, 0, measuredHeight / 4);
            mTextView.setTextSize(measuredHeight / 5);
        } else if (mBitmap != null) {
            mImageView.setScaleType(ScaleType.FIT_CENTER);
            mImageView.setBackgroundDrawable(roundedBackgroundDrawable);
            mBitmap = ThumbnailUtils.extractThumbnail(mBitmap, measuredWidth, measuredHeight);
            final RoundedBitmapDrawable roundedBitmapDrawable = RoundedBitmapDrawableFactory.create(getResources(),
                    mBitmap);
            roundedBitmapDrawable.setCornerRadius((measuredHeight + measuredWidth) / 4);
            mImageView.setImageDrawable(roundedBitmapDrawable);
        }
        resetValuesState(false);
    }

    public void setTextAndBackgroundColor(final CharSequence text, final int backgroundColor) {
        resetValuesState(true);
        while (getCurrentView() != mTextView)
            showNext();
        this.mBackgroundColor = backgroundColor;
        mText = text;
        final int height = getHeight(), width = getWidth();
        if (height != 0 && width != 0)
            drawContent(width, height);
    }

    public void setImageResource(final int imageResId, final int backgroundColor) {
        resetValuesState(true);
        while (getCurrentView() != mImageView)
            showNext();
        mImageResId = imageResId;
        this.mBackgroundColor = backgroundColor;
        final int height = getHeight(), width = getWidth();
        if (height != 0 && width != 0)
            drawContent(width, height);
    }

    public void setImageBitmap(final Bitmap bitmap) {
        setImageBitmapAndBackgroundColor(bitmap, 0);
    }

    public void setImageBitmapAndBackgroundColor(final Bitmap bitmap, final int backgroundColor) {
        resetValuesState(true);
        while (getCurrentView() != mImageView)
            showNext();
        this.mBackgroundColor = backgroundColor;
        mBitmap = bitmap;
        final int height = getHeight(), width = getWidth();
        if (height != 0 && width != 0)
            drawContent(width, height);
    }

    private void resetValuesState(final boolean alsoResetViews) {
        mBackgroundColor = mImageResId = 0;
        mBitmap = null;
        mText = null;
        if (alsoResetViews) {
            mTextView.setText(null);
            mTextView.setBackgroundDrawable(null);
            mImageView.setImageBitmap(null);
            mImageView.setBackgroundDrawable(null);
        }
    }

    public ImageView getImageView() {
        return mImageView;
    }

    public TextView getTextView() {
        return mTextView;
    }

}

3. PagerSlidingTabStripと呼ばれる、それを行う素晴らしいライブラリを見つけました。ただし、ネイティブのスタイルを設定する公式の方法は見つかりませんでした。

もう 1 つの方法は、Android-Studio 内で直接利用できる、「SlidingTabLayout」と呼ばれる Google のサンプルを見ることです。それがどのように行われたかを示しています。

編集: 「PagerSlidingTabStrip」とも呼ばれる、#3のより良いライブラリがここにあります。

于 2014-12-25T08:23:33.863 に答える
2

次のことができます。

  1. RecyclerView の左端に、文字インデックスを保持する TextView を作成します。
  2. Recycler ビューの上部 (それをラップするレイアウト内) に TextView を配置して、手順 1 で作成したものをカバーします。これがスティッキー ビューになります。
  3. RecyclerView に OnScrollListener を追加します。メソッド onScrolled() で、手順 2 で作成した TextView を firstVisibleRow から取得した参照テキストに設定します。ここまでは、移行の影響なしで、粘着性のあるインデックスが表示されます。
  4. フェードイン/フェードアウト遷移効果を追加するには、currentFirstVisibleItem の前の項目が前の文字リストの最後であるか、または secondVisibleItem が新しい文字の最初の項目であるかを確認するロジックを開発します。これらの情報に基づいて、スティッキー インデックスを表示/非表示にし、行インデックスを逆にして、この最後のアルファ効果を追加します。

       if (recyclerView != null) {
        View firstVisibleView = recyclerView.getChildAt(0);
        View secondVisibleView = recyclerView.getChildAt(1);
    
        TextView firstRowIndex = (TextView) firstVisibleView.findViewById(R.id.sticky_row_index);
        TextView secondRowIndex = (TextView) secondVisibleView.findViewById(R.id.sticky_row_index);
    
        int visibleRange = recyclerView.getChildCount();
        int actual = recyclerView.getChildPosition(firstVisibleView);
        int next = actual + 1;
        int previous = actual - 1;
        int last = actual + visibleRange;
    
        // RESET STICKY LETTER INDEX
        stickyIndex.setText(String.valueOf(getIndexContext(firstRowIndex)).toUpperCase());
        stickyIndex.setVisibility(TextView.VISIBLE);
    
        if (dy > 0) {
            // USER SCROLLING DOWN THE RecyclerView
            if (next <= last) {
                if (isHeader(firstRowIndex, secondRowIndex)) {
                    stickyIndex.setVisibility(TextView.INVISIBLE);
                    firstRowIndex.setVisibility(TextView.VISIBLE);
                    firstRowIndex.setAlpha(1 - (Math.abs(firstVisibleView.getY()) / firstRowIndex.getHeight()));
                    secondRowIndex.setVisibility(TextView.VISIBLE);
                } else {
                    firstRowIndex.setVisibility(TextView.INVISIBLE);
                    stickyIndex.setVisibility(TextView.VISIBLE);
                }
            }
        } else {
            // USER IS SCROLLING UP THE RecyclerVIew
            if (next <= last) {
                // RESET FIRST ROW STATE
                firstRowIndex.setVisibility(TextView.INVISIBLE);
    
                if ((isHeader(firstRowIndex, secondRowIndex) || (getIndexContext(firstRowIndex) != getIndexContext(secondRowIndex))) && isHeader(firstRowIndex, secondRowIndex)) {
                    stickyIndex.setVisibility(TextView.INVISIBLE);
                    firstRowIndex.setVisibility(TextView.VISIBLE);
                    firstRowIndex.setAlpha(1 - (Math.abs(firstVisibleView.getY()) / firstRowIndex.getHeight()));
                    secondRowIndex.setVisibility(TextView.VISIBLE);
                } else {
                    secondRowIndex.setVisibility(TextView.INVISIBLE);
                }
            }
        }
    
        if (stickyIndex.getVisibility() == TextView.VISIBLE) {
            firstRowIndex.setVisibility(TextView.INVISIBLE);
        }
    }
    

上記のロジックを実行するコンポーネントを開発しました。ここで見つけることができます: https://github.com/edsilfer/sticky-index

于 2015-06-06T05:50:59.837 に答える
1

返信が遅くなりましたが、私の回答が誰かの役に立てば幸いです。私にもそのような仕事がありました。スティッキーヘッダーの回答と例を探していましたが、recyclerView. Saber Solookiの記事「Sticky Header For RecyclerView」で、最も簡単な解決策を見つけました。

ここに画像の説明を入力

この例に基づいて、アプリケーション用の連絡先モジュールを作成しました。非常に簡単です。

ここに画像の説明を入力

于 2019-10-10T10:41:20.193 に答える
0

アプリのソース コードは常に開始するのに適した場所です。

于 2014-12-23T15:02:14.127 に答える