列表型布局套路总结

手机屏幕尺寸有限,几乎每个 app 的内容区,都会用列表呈现。不信可以打开你手机里的任意一个 app 看看,99%的 app 的内容区都能被抽象成一个大列表。只是根据内容的丰富度,列表的复杂度有所不同。内容区的结构骨架,基本上就是由列表布局奠定的。从这点看,虽然还有其他各种类型的布局,但是它们都没有列表型的布局来得重要。
总的来说,必须知道的有 如何应对多类型复杂结构几个滚动相关的实用的特别交互、和使用 RecyclerView 注意要填的坑。这些足以覆盖 90% 的业务场景。
(Android 的列表布局多由RecyclerView实现,以下内容均基于RecyclerView)

应对复杂结构列表

我安利 MultiType

从前,比如我们写一个类似微博列表页面,这样的列表是十分复杂的:有纯文本的、带转发原文的、带图片的、带视频的、带文章的等等,甚至穿插一条可以横向滑动的好友推荐条目。不同的 item 类型众多,而且随着业务发展,还会更多。如果我们使用传统的开发方式,经常要做一些繁琐的工作,代码可能都堆积在一个 Adapter 中:我们需要覆写 RecyclerView.Adapter 的 getItemViewType 方法,罗列一些 type 整型常量,并且 ViewHolder 转型、绑定数据也比较麻烦。一旦产品需求有变,或者产品设计说需要增加一种新的 item 类型,我们需要去代码堆里找到原来的逻辑去修改,或找到正确的位置去增加代码。这些过程都比较繁琐,侵入较强,需要小心翼翼,以免改错影响到其他地方。
现在好了,我们有了 MultiType,简单来说,MultiType 就是一个多类型列表视图的中间分发框架,它能帮助你快速并且清晰地开发一些复杂的列表页面,数据驱动视图。 它本是为聊天页面开发的,聊天页面的消息类型也是有大量不同种类,且新增频繁,而 MultiType 能够轻松胜任。

列表滚动速度控制

通过自定义 LayoutManager 的 SmoothScroller。

public class WrapContentLinearLayoutManager extends LinearLayoutManager {
    private static float MILLISECONDS_PER_INCH = 25f; //default is 25f (bigger = slower)
    public WrapContentLinearLayoutManager(Context context) {
        super(context);
    }

    public WrapContentLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
    }

    public WrapContentLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {

        final LinearSmoothScroller linearSmoothScroller = new LinearSmoothScroller(recyclerView.getContext()) {

            @Override
            public PointF computeScrollVectorForPosition(int targetPosition) {
                return WrapContentLinearLayoutManager.this.computeScrollVectorForPosition(targetPosition);
            }

            @Override
            protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
                return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
            }
        };

        linearSmoothScroller.setTargetPosition(position);
        startSmoothScroll(linearSmoothScroller);
    }

    @Override
    public void onScrollStateChanged(int state) {
        super.onScrollStateChanged(state);
        if (state == RecyclerView.SCROLL_STATE_IDLE) { // on scroll stop
            setSpeedSlow(25f);
        }
    }

    public void setSpeedSlow(float msPerInch) {
        MILLISECONDS_PER_INCH = msPerInch;
    }
}

列表项的自我位置感知

用来应对当 XXX 可见/滑到屏幕中间/正在滑进屏幕/正在滑出屏幕 就要干 XXX 这类奇怪需求。

    /**
     * 获得给定view的可见度,0~100
     */
    public int getVisibilityPercents(View view) {
        if(SHOW_LOGS) Logger.v(TAG, ">> getVisibilityPercents view " + view);

        int percents = 100;
        Rect mCurrentViewRect = new Rect();;
        view.getLocalVisibleRect(mCurrentViewRect);
        if(SHOW_LOGS) Logger.v(TAG, "getVisibilityPercents mCurrentViewRect top " + mCurrentViewRect.top + ", left " + mCurrentViewRect.left + ", bottom " + mCurrentViewRect.bottom + ", right " + mCurrentViewRect.right);

        int height = view.getHeight();
        if(SHOW_LOGS) Logger.v(TAG, "getVisibilityPercents height " + height);

        if(viewIsPartiallyHiddenTop(mCurrentViewRect)){
            // view is partially hidden behind the top edge
            percents = (height - mCurrentViewRect.top) * 100 / height;
        } else if(viewIsPartiallyHiddenBottom(mCurrentViewRect,height)){
            percents = mCurrentViewRect.bottom * 100 / height;
        }

        if(SHOW_LOGS) Logger.v(TAG, "<< getVisibilityPercents, percents " + percents);

        return percents;
    }
      private boolean viewIsPartiallyHiddenBottom(Rect mCurrentViewRect ,int height) {
        return mCurrentViewRect.bottom > 0 && mCurrentViewRect.bottom < height;
    }

    private boolean viewIsPartiallyHiddenTop(Rect mCurrentViewRect ) {
        return mCurrentViewRect.top > 0;
    }

填埋官方遗留之坑

规避 RecyclerView 的一个不会报到Java层的bug(直接崩溃,不会有弹窗提示崩溃),因为在底层直接抛了IndexOutOfBoundsException。可以关注AOSP关于该Bug的记录,跟踪是否有fix:https://issuetracker.google.com/issues/37007605#hc141

暂时只能在 LinearLayoutManager 类对应的方法中 catch 掉等官方解决:

    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        try {
            super.onLayoutChildren(recycler, state);
        } catch (IndexOutOfBoundsException e) {
            e.printStackTrace();
        }
    }

    @Override
    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
        try {
            return super.scrollVerticallyBy(dy, recycler, state);
        } catch (IndexOutOfBoundsException e) {
            e.printStackTrace();
        }
        return 0;
    }
阅读更多

没有更多推荐了,返回首页