RecyclerView实现画廊效果|列表选中位置居中|列表位置固定|第一个项目居中|列表平滑滚动

RecyclerView实现画廊效果

目录
RecyclerView实现画廊效果 1
目标的画廊效果 2
RecyclerView提供的默认效果 2
关键要点 2
实现步骤 2
4.1添加依赖 2
4.2定义子视图布局 3
4.3定义Acitivity布局 3
4.4初始化RecyclerView 4
4.5自定义ItemDecoration 5
4.6自定义LinearLayoutManager 7
代码分享 10
目标的画廊效果
希望实现选中的布局始终处于屏幕位置的中央,同时滑动后能够停留在屏幕位置的中央,如以下效果图所示:
快速滑动的效果
快速滑动
慢速滑动的效果
慢速滑动的效果
最后一张图片停止的效果,居中显示
最后一个图片居中显示
第一张图停住效果
第一张图停住效果,可以居中显示

RecyclerView提供的默认效果
RecyclerView提供的默认效果是从第一个显示,而且滑动结束后可以停留在任意位置。无法确保选中的位置居中显示。

关键要点
要实现这个目标,通过阅读多篇其他人的介绍文档,以及整理其他文章未重点说明的内容,总结起来主要的重点难点包括以下内容:

序号重点难点思路
1实现第一个视图和最后一个视图显示时位置居中针对第一个视图和最后一个视图设置边距,确保显示时能够居中
2计算第一个视图和最后一个视图合适的边距使用RecyclerView布局的宽度的1/2 – 子视图布局的宽度的1/2
3子视图尚无法测量时,获取其布局宽度通过子视图绘制完毕后测量
4实现平滑的滑动效果使用SmoothScroller设置恰当的滑动速度,也可以根据待滑动的距离的差异设置差异化速度,避免滑动时间过长

4.实现步骤
实现步骤中仅列举重要的代码片段的讲解,最后会提供全部的详细源码供参考。
4.1添加依赖

implementation 'androidx.appcompat:appcompat:1.1.0'
//Glide
implementation ("com.github.bumptech.glide:glide:4.11.0") {
    exclude group: "com.android.support"
}

4.2定义子视图布局
布局由一个图片标题和图片构成

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="100dp"
    android:layout_height="125dp">
    <ImageView
        android:id="@+id/imageview"
        android:layout_width="100dp"
        android:layout_height="100dp"/>
    <TextView
        android:id="@+id/textview"
        android:layout_width="100dp"
        android:layout_height="50dp"
        android:gravity="center"/>
</LinearLayout>

4.3定义Acitivity布局
设置一个RecyclerView,本次重写了RecyclerView为了实现选中位置放大,以及两侧位置相应缩小的效果

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="基于RecyclerView实现照片画廊效果"
        android:layout_marginBottom="100dp"/>

    <com.guo.picturegallery.GalleryRecyclerView
        android:id="@+id/recycler_view_gallery"
        android:layout_width="match_parent"
        android:layout_height="250dp"/>

</LinearLayout>

4.4初始化RecyclerView
设置Adapter , LayoutManager 和Decoration

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    /**
     * 对RecyclerView初始化
     */
    RecyclerView recyclerViewGallery = findViewById(R.id.recycler_view_gallery);
    PictureAdapter adapter = new PictureAdapter();
    adapter.setDataList(getStarsList());
    /**
     * 基于自定义的LayoutManager实现子视图在RecyclerView在屏幕的位置居中
     */
    LinearLayoutManager layoutManager = new GalleryLayoutManager(this,RecyclerView.HORIZONTAL,false);
    recyclerViewGallery.setLayoutManager(layoutManager);

    recyclerViewGallery.addItemDecoration(new HorizontalDecoration(10));
    recyclerViewGallery.setAdapter(adapter);
    LinearSnapHelper snapHelper = new LinearSnapHelper();
    snapHelper.attachToRecyclerView(recyclerViewGallery);
    recyclerViewGallery.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
        }
    });
    /**
     * 点击屏幕中的非居中位置时,可以使点击的项目平滑移动至屏幕中央
     */
    adapter.setOnItemClickListener(new BaseAdapter.OnItemClickListener() {
        @Override
        public void onItemClick(int pos) {
            recyclerViewGallery.smoothScrollToPosition(pos);
        }
    });
}

4.5自定义ItemDecoration
自定义ItemDecoration,设置第一个视图和最后一个视图偏移的距离,确保第一个视图和最后一个视图在屏幕中居中。
第一个项目和最后一个项目边距的计算逻辑
如上图所示,要实现第一个视图和最后一个视图居中,最重要的是准确计算边距,边距为RecyclerView布局宽度与子视图宽度差的一半。具体实现代码如下:

/**
 * 自定义ItemDecoration,设置第一个视图和最后一个视图偏移的距离,确保第一个视图和最后一个视图在屏幕中居中<P/>
 * @author mailanglideguozhe 20210520
 */
public class HorizontalDecoration extends RecyclerView.ItemDecoration {
    private int space = 0;
    /**
     * 第一个视图和最后一个视图偏移的距离
     */
    private int distance = 0;
    private static final String TAG = "HorizontalDecoration";

    /**
     * 设置RecyclerView子视图的边距,本示例仅用于定义两个子视图之间的边距,为space*2
     * @param space 设置的边距
     */
    public HorizontalDecoration(int space) {
        this.space = space;
    }

    /**
     * 获取子视图的边距
     * @param view 子视图
     * @param parent RecyclerView对象
     */
    @Override
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        int pos = parent.getChildAdapterPosition(view);
        RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams)view.getLayoutParams();
        /**
         * 仅计算一次偏移边距即可,无需重复计算<P/>
         * 由于此时View并未完成测量,无法基于测量获取其宽度;思路是在view绘制完成后再进行测量,并设置第一个的左边距
         */
        if(distance <= 0){
            view.post(new Runnable() {
                @Override
                public void run() {
                    distance = dtDistance(parent,view);
                    //设置第一个视图的左边距
                    View childView = parent.getChildAt(0);
                    RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams)childView.getLayoutParams();
                    layoutParams.setMargins(distance,0,space,0);
                    childView.setLayoutParams(layoutParams);
                    //打开后默认显示第一个(居中显示)
                    parent.scrollToPosition(0);
                }
            });
        }
        /**
         * 通过设置Item左右边距实现第一个左侧和最后一个右侧设置边距,确保显示的视图位于屏幕中间
         */
        int itemCount = parent.getAdapter().getItemCount();
        if(pos == 0){
            layoutParams.setMargins(distance,0,space,0);
        }else if(pos == itemCount-1){
            layoutParams.setMargins(space,0,distance,0);
        }else {
            layoutParams.setMargins(space,0,space,0);
        }
        /**
         * 更新子视图的边距
         */
        view.setLayoutParams(layoutParams);
        super.getItemOffsets(outRect, view, parent, state);
    }

    /**
     * 为了使第一个和最后一个item居中,需要设置相应偏移,偏移量为RecyclerView布局宽度减去子视图的一半<P/>
     * 注意此处由于子视图并未实例化完成,无法通过测量得知其宽度,故需要直接获取布局宽度参数得知<P/>
     */
    public int dtDistance(RecyclerView recyclerView , View childView){
        int width = recyclerView.getWidth() != 0 ? recyclerView.getWidth():recyclerView.getMeasuredWidth();
        //此处需要获取子视图布局的宽度,注意此处由于子视图并未实例化完成,无法通过测量得知其宽度
        childView.getMeasuredWidth();
        int childWidth = childView.getWidth();
        //第一个视图左侧偏移量,最后一个视图右侧偏移量
        return width/2 -childWidth/2;
    }
}

4.6自定义LinearLayoutManager
为了点击item使相应的item居中显示,且实现平滑的滚动,重写LinearLayoutManager。原始的RecyclerView是可以停留在任意位置的,在目标场景下,必须确保有一个视图是在RecyclerView中间位置的,因为在原始逻辑的基础上需要重新校正RecyclerView子视图的位置。校正位置校正距离计算逻辑
距离的计算逻辑如下:
实现代码如下:

/**
 * 为了点击item使相应的item居中显示,且实现RecyclerView的平缓滑动,重写LinearLayoutManager<P/>
 * @author mailanglideguozhe 20210520
 */
public class GalleryLayoutManager extends LinearLayoutManager {
    public GalleryLayoutManager(Context context) {
        super(context);
    }

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

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

    /**
     * 重写smoothScrollToPosition方法,实现平滑滑动及停留在中间位置
     * @param recyclerView
     * @param state
     * @param position 目标位置
     */
    @Override
    public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
        RecyclerView.SmoothScroller smoothScroller = new RecyclerviewSmoothScroller(recyclerView.getContext());
        smoothScroller.setTargetPosition(position);
        startSmoothScroll(smoothScroller);
    }

    /**
     * 重写LinearSmoothScroller自定义 中间对齐需要的校正位置
     */
    private static class RecyclerviewSmoothScroller extends LinearSmoothScroller{
        public RecyclerviewSmoothScroller(Context context) {
            super(context);
        }

        /**
         * 为了确保子视图在显示的位置中间位置,需要设置应当校正子视图的位置。<P/>
         * 子视图校正需要移动的距离为Recycler布局中间位置与子视图中间位置的距离<P/>
         * @param viewStart 子视图的左侧位置
         * @param viewEnd 子视图的右侧位置
         * @param boxStart RecyclerView视图的左侧位置
         * @param boxEnd RecyclerView视图的左侧位置
         * @return 返回子视图校正需要移动的距离
         */
        @Override
        public int calculateDtToFit(int viewStart, int viewEnd, int boxStart, int boxEnd, int snapPreference) {
            return (boxStart + (boxEnd - boxStart) / 2) - (viewStart + (viewEnd - viewStart) / 2);
        }

        /**
         * 计算滑动的速度,返回1px滑动所需的时间,举例 如返回0.8f,即滑动1000个像素点距离需要0.8s<P/>
         * @param displayMetrics
         * @return 返回1px滑动所需的时间,单位ms
         */
        @Override
        protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
            return super.calculateSpeedPerPixel(displayMetrics);
        }
    }
}

至此实现此功能的核心逻辑全部讲解完毕

代码分享

本文是第一次撰写技术分享文章,由于个人经验不足,难免出现错漏,欢迎大家指正。关于Android源码分析过程才是本源,由于时间问题,将放在下一篇介绍,以下是该项目源码,如果你觉得效果满意,还请帮忙点赞。如有宝贵建议,欢迎留言交流。
https://gitee.com/com_mailanglidegezhe/gallery-recycler-view.git

  • 10
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
实现 RecyclerView 滚动条高度自定义,你可以考虑使用一个自定义的滚动条 View,然后通过监听 RecyclerView滚动事件来控制滚动条的位置和高度。 首先,你需要在 RecyclerView 的布局文件中添加一个自定义的滚动条 View,例如: ```xml <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" /> <com.example.customscrollbar.CustomScrollBar android:id="@+id/custom_scroll_bar" android:layout_width="10dp" android:layout_height="match_parent" android:layout_alignParentRight="true" android:layout_marginRight="10dp" /> </RelativeLayout> ``` 其中,CustomScrollBar 是自定义的滚动条 View,它的宽度可以根据需求自行设置。 然后,在 RecyclerView 的代码中,你需要监听 RecyclerView滚动事件,计算滚动条的位置和高度,然后更新 CustomScrollBar 的位置和高度。例如: ```java recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); // 计算当前可见的 item 数量 int visibleItemCount = recyclerView.getLayoutManager().getChildCount(); // 计算所有 item 的数量 int totalItemCount = recyclerView.getLayoutManager().getItemCount(); // 计算第一个可见的 item 的位置 int firstVisibleItemPosition = ((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition(); // 计算滚动条的高度 int scrollBarHeight = (int) ((float) visibleItemCount / (float) totalItemCount * recyclerView.getHeight()); // 计算滚动条的位置 int scrollBarTop = (int) ((float) firstVisibleItemPosition / (float) totalItemCount * recyclerView.getHeight()); // 更新滚动条的位置和高度 customScrollBar.setScrollBarTop(scrollBarTop); customScrollBar.setScrollBarHeight(scrollBarHeight); } }); ``` 在这段代码中,我们使用了 RecyclerView 的 LayoutManager 中的方法来计算当前可见的 item 数量、所有 item 的数量和第一个可见的 item 的位置,然后根据这些数据计算出滚动条的位置和高度,最后更新 CustomScrollBar 的位置和高度。 最后,你需要在 CustomScrollBar 中实现 setScrollBarTop() 和 setScrollBarHeight() 方法,用于设置滚动条的位置和高度。例如: ```java public void setScrollBarTop(int top) { setY(top); } public void setScrollBarHeight(int height) { getLayoutParams().height = height; requestLayout(); } ``` 这样,你就可以实现 RecyclerView 滚动条高度自定义的效果了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值