Подтвердить что ты не робот

Как имитировать элементы списка listView, например, в приложении Lollipop contacts?

Фон

Недавно Google опубликовала новую версию Android, в которой есть контактное приложение, которое выглядит так:

enter image description here

Проблема

Мне нужно подражать этому списку, но я не могу понять, как это сделать.

Это состоит из 3 основных компонентов:

  • титульные листы, которые остаются до тех пор, пока верхний контакт имеет имя, которое начинается с этой буквы.

  • Круговые фотографии или круг + письмо (для контактов, у которых нет фотографий)

  • PagerTitleStrip, который имеет равномерное расстояние между элементами и пытается показать их все на экране (или позволяет прокручивать, если необходимо).

Что я нашел

  • Существует множество сторонних библиотек, но они обрабатывают stickey-заголовки, которые являются частью элементов самого списка. Они не имеют названия слева, но в верхней части элементов.

    Здесь левая сторона движется иначе, чем правая. Он может придерживаться, и если нет необходимости вставлять (потому что в этом разделе есть один элемент, например), он прокручивается.

  • Я заметил, что для круговых фотографий я могу использовать новый класс (?), называемый RoundedBitmapDrawableFactory "(который создает" RoundedBitmapDrawable"). Это, вероятно, позволит мне красиво поставить фотографию в круглую форму. Тем не менее, он не будет работать для писем (используется для контактов, у которых нет фотографии), но я думаю, что могу поместить textView сверху и установить фон в цвет.

    Кроме того, я заметил, что для того, чтобы хорошо использовать RoundedBitmapDrawable (чтобы сделать его действительно круговым), я должен предоставить ему растровое изображение с квадратным размером. В противном случае он будет иметь странную форму.

  • Я попытался использовать setTextSpacing", чтобы свести к минимуму пространство между элементами, но он, похоже, не работает. Я также не мог найти способ подстроить/настроить PagerTitleStrip как в приложении для контактов.

    Я также пробовал использовать PagerTabStrip", но это также не помогло.

Вопрос

Как вы можете подражать тому, как Google внедрил этот экран?

Более конкретно:

  • Как я могу заставить левую сторону вести себя как в приложении контактов?

  • Это лучший способ реализовать круговую фотографию? Действительно ли мне нужно обрезать растровое изображение на квадрат перед использованием специальной возможности рисования? Существуют ли какие-либо рекомендации по дизайну, для которых используются цвета? Есть ли более официальный способ использования ячейки круг-текст?

  • Как вы создаете стиль PagerTitleStrip, чтобы иметь тот же внешний вид и чувствовать себя как в приложении контактов?


Проект Github

EDIT: для # 1 и # 2 я сделал проект в Github, здесь. К сожалению, я также нашел важную ошибку, поэтому я также опубликовал об этом здесь.

4b9b3361

Ответ 1

ok, мне удалось решить все проблемы, о которых я писал:

1.I изменил способ работы сторонней библиотеки (я не помню, откуда я получил библиотеку, но этот очень похож), изменив расположение каждой строки так, чтобы заголовок находился слева от самого содержимого. Это всего лишь вопрос 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. Однако не нашлось официального способа стиля родного.

Другой способ - посмотреть образец Google, который доступен прямо в Android-Studio, и называется "SlidingTabLayout". Он показывает, как это делается.

EDIT: лучшая библиотека для # 3 - здесь, также называемая "PagerSlidingTabStrip".

Ответ 2

Вы можете сделать следующее:

  • В самой левой части вашего RecyclerView создается TextView, который будет содержать индекс букв;
  • В верхней части окна Recycler (в макете, который обертывает его), поместите TextView, чтобы покрыть тот, который вы создали на шаге 1, это будет липкий,
  • Добавьте OnScrollListener в свой RecyclerView. В методе onScrolled() установите TextView, созданный на шаге 2, для справочного текста, взятого из firstVisibleRow. До тех пор, пока вы не получите стильный индекс, без последствий перехода;
  • Чтобы добавить эффект перехода fade in/out, создайте логику, которая проверяет, является ли элемент предыдущего текущегоFirstVisibleItem последним из предыдущего списка букв, или если 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

Ответ 4

Я отвечаю так поздно, но надеюсь, мой ответ кому-нибудь поможет. У меня тоже была такая задача. Я искал ответы и примеры липкого заголовка, но для recyclerView. Лучшее и простое решение я нашел в статье "Липкий заголовок для RecyclerView" Сабер Солуки.

enter image description here

Основываясь на этом примере, я сделал свой контактный модуль для своего приложения, это очень просто.

enter image description here