RecyclerView使用技巧总结

前言

最近在做一个音乐列表,希望正在播放的歌曲始终在列表中居中显示,类似下面的样子。
在这里插入图片描述
切换上一曲下一曲时始终能保持当前的歌曲始终居中。RecyclerView有两个函数smoothScrollToPosition和scrollToPosition都能滚动到指定的位置,但是并不能保持居中,自己做位置记忆的话又很麻烦,自己尝试了半天没有好方法,最终参考了一位大佬的文章。

https://blog.csdn.net/iblade/article/details/90449089

自定义布局管理器

继承LinearLayoutManager,重新smoothScrollToPosition方法实现滚动到指定的position

public class CenterLayoutManager extends LinearLayoutManager {
    public CenterLayoutManager(Context context) {
        super(context);
    }

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

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

    @Override
    public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
        super.smoothScrollToPosition(recyclerView, state, position);
        RecyclerView.SmoothScroller smoothScroller = new CenterSmoothScroller(recyclerView.getContext());
        smoothScroller.setTargetPosition(position);
        startSmoothScroll(smoothScroller);
    }

    private static class CenterSmoothScroller extends LinearSmoothScroller{

        public CenterSmoothScroller(Context context) {
            super(context);
        }
		// 正是该方法实现居中显示
        @Override
        public int calculateDtToFit(int viewStart, int viewEnd, int boxStart, int boxEnd, int snapPreference) {
            return (boxStart + (boxEnd - boxStart) / 2) - (viewStart + (viewEnd - viewStart) / 2);
        }
    }
}

接下来我们在Adapter中通过一个变量记录下当前正在播放的歌曲的位置,需要时滚动到该位置即可。

public class PinAdapter extends RecyclerView.Adapter<PinAdapter.ViewHolder>{
    private static final String TAG = "PinAdapter";

    private Context context;
    private List<String> mList;
	// 记录当前歌曲位置
    public int mPosition = 0;

    public PinAdapter(Context context, List<String> mList) {
        this.context = context;
        this.mList = mList;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new ViewHolder(LayoutInflater.from(context).inflate(R.layout.simple_text_layout,parent,false));
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        if (position == mPosition){
            holder.textView.setTextColor(Color.GREEN);
        }else {
            holder.textView.setTextColor(Color.WHITE);
        }
        holder.textView.setText(mList.get(position));
    }

    @Override
    public int getItemCount() {
        return mList.size();
    }

    class ViewHolder extends RecyclerView.ViewHolder{

        private TextView textView;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            textView = itemView.findViewById(R.id.tv_simple_text);
        }
    }
}

设置适配器和布局管理器

        pinAdapter = new PinAdapter(this,list);
        recyclerView.setLayoutManager(new CenterLayoutManager(this));
        recyclerView.setAdapter(pinAdapter);
        // 为了让每一个item完整显示
        PagerSnapHelper helper = new PagerSnapHelper();
        helper.attachToRecyclerView(recyclerView);

PagerSnapHelper类是为了辅助让每一首歌都完整显示,不出现因为RecyclerView滚动时显示部分的情况,这个类是原生的可以直接使用。我们点击上一曲下一曲时就可以进行滚动并刷新了

            recyclerView.smoothScrollToPosition(pinAdapter.mPosition);
            pinAdapter.notifyDataSetChanged();

监听RecyclerView滚动

滑动歌曲时,我们希望在停止滑动一段时间后能回到正在播放的歌曲的位置,这时我们可以通过Handler来实现,停止滚动后发送一个延迟信息,比如3s后如果该信息没有被取消那么就回到播放位置,开始滑动时取消该消息。首先给RecyclerView添加一个监听器

        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                // 停止滚动
                if (newState == RecyclerView.SCROLL_STATE_IDLE){
                    Message message = mHanler.obtainMessage(1);
                    mHanler.sendMessageDelayed(message,3000);
                }else {
                    mHanler.removeMessages(1);
                }
            }

            @Override
            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
            }
        });

方法onScrollStateChanged是在RecyclerView停止滑动时被调用,一般来说有如下几种状态

//停止滚动
public static final int SCROLL_STATE_IDLE = 0;
 
//正在被外部拖拽,一般为用户正在用手指滚动
public static final int SCROLL_STATE_DRAGGING = 1;
 
//自动滚动开始
public static final int SCROLL_STATE_SETTLING = 2;

我们只需要在开始滚动时移除handler消息,停止滚动后创建消息即可。
onScrolled函数一般是用来计算滚动距离之类的,比如希望将RecyclerView和一个外部的滚动条联系起来,我们则可以通过该函数计算,dx是水平滚动的距离,dy是垂直滚动的距离,正负代表滚动的方向,比如dy>0时表示向上滚动,dy<0时表示向下滚动。还可以通过其他两个函数去计算它的总长度和偏移量。

            @Override
            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                // RecyclerView总长度
                int max = recyclerView.computeVerticalScrollRange();
                // 当前位置偏移量
                int offset = recyclerView.computeVerticalScrollOffset();
            }

2、item整体滑动

item滑动的时候会有些会出现部分,但是我们有时候希望它完整的显示,类似图片轮播的效果,只需要两行代码搞定。

        PagerSnapHelper helper = new PagerSnapHelper();
        helper.attachToRecyclerView(recyclerView);

3、拖动删除和拖动交换位置等

要实现RecycleView拖拽移动、删除等操作,需要借助到另一个类ItemTouchHelper,借助它能让我们轻松实现上诉功能。
3.1、创建一个ItemTouchHelper的实例

 ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new ItemTouchHelper.Callback(){

            @Override
            public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
                return 0;
            }
            @Override
            public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
                return false;
            }

            @Override
            public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {

            }
        });

我们给ItemTouchHelper添加一个回调函数,一般有三个方法

getMovementFlags:在设个函数中设置拖拽、滑动等标志

onMove:当我们拖动一个item时,会回调这个方法,因此可以在这个方法中处理一些拖动逻辑,比如交换两个item。

onSwiped:处理侧滑,比如删除当前的item。

3.2、处理侧滑删除

            @Override
            public int getMovementFlags(@NonNull  RecyclerView recyclerView, @NonNull  RecyclerView.ViewHolder viewHolder) {
                // 指定左右滑动可以删除
                int swiped = ItemTouchHelper.RIGHT | ItemTouchHelper.LEFT;
                return makeMovementFlags( 0 ,swiped);
            }

在getMovementFlags函数中设置侧滑标志,我们指定了左右滑动都可以删除,然后通过函数makeMovementFlags进行设置,它需要两个参数,第一个是拖动的标志,这里我们还没有设置,传入一个0即可,第一个是侧滑的标志。,现在就可以实现左右滑动删除了。

但是我们发现,删除后的位置并没有数据填充进去,这时就需要借助到另一个工具函数onSwiped了,我们在这个函数中删除掉对应的数据,并通知数据源改变。

@Override
public void onSwiped(@NonNull  RecyclerView.ViewHolder viewHolder, int direction) {
    int position = viewHolder.getAdapterPosition();
    arrayList.remove(position);
    adapter.notifyItemRemoved(position);
}

现在视图上数据删除后,同时在数据源也删除掉,并通知数据更新,就会看到其他的数据填充进来了。

3.3、数据高亮

当我们拖动一个item时,如果希望该item高亮显示,删除后恢复,那需要重写回调函数中的onSelectedChanged和clearView。

@Override
public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState) {
    if (actionState != ItemTouchHelper.ACTION_STATE_IDLE){
        viewHolder.itemView.setBackgroundColor(Color.parseColor("#303F9F"));
    }
}

@Override
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
    super.clearView(recyclerView,viewHolder);
    // 设置回原来的颜色
    viewHolder.itemView.setBackgroundColor(Color.TRANSPARENT);
}

ItemTouchHelper.ACTION_STATE_IDLE表示item当前的状态空闲,它还有其他的

状态如 ItemTouchHelper.ACTION_STATE_SWIPED, ItemTouchHelper.ACTION_STATE_DRAGED等,分别表示滑动拖动等标志,其他的状态就不介绍了,我们再根据它的状态设置不同的背景即可。

3.4、拖动交换

首先,在 getMovementFlags函数中设置拖动的标志

@Override
public int getMovementFlags(@NonNull  RecyclerView recyclerView, @NonNull  RecyclerView.ViewHolder viewHolder) {
    int swiped = ItemTouchHelper.RIGHT | ItemTouchHelper.LEFT;
    int dragFlag = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
    return makeMovementFlags(dragFlag,swiped);
}

因为我的布局是线性的,所以我知识给拖动指定了上下两个方向,如果是其他布局也可以自己决定方向,比如可以通过

if(recyclerView.getLayoutManager() instanceof GridLayoutManager){
    // 设置上下左右滑动的标志
    int dragFlag = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.TOP | ItemTouchHelper.RIGHT;
}

来获取当前的布局状态,再根据具体布局决定方向了。

接下来是重写onMove函数,处理拖动交换逻辑,其实就是交换数据列表中两个元素的位置。

            @Override
            public boolean onMove(@NonNull  RecyclerView recyclerView, @NonNull  RecyclerView.ViewHolder viewHolder, @NonNull  RecyclerView.ViewHolder target) {
                int oldPosition = viewHolder.getAdapterPosition();
                int newPosition = target.getAdapterPosition();
                if (oldPosition<newPosition){
                    for (int i = oldPosition; i < newPosition; i++) {
                        Collections.swap(arrayList,i,i+1);
                    }
                }else {
                    for (int i = oldPosition; i < newPosition; i--) {
                        Collections.swap(arrayList,i,i-1);
                    }
                }
                adapter.notifyItemMoved(oldPosition,newPosition);
                return true;
            }

逻辑也很简单,获取item新、旧位置,然后交换对应数据源中数据的位置,最后更新一下数据源就可以了。

最后,将ItemTouchHelper关联到recycleView就起作用了。

itemTouchHelper.attachToRecyclerView(mRecycle);

多布局显示

前段时间需要做一个城市首字母索引的列表,样式如下

这里需要用到RecyclerView的多布局显示,下面就记录一下
1、数据构造
我们需要构造一个列表,里面包含了城市的首字母和城市,为了方便做区分,将字母定义为char类型,将城市封装一下

public class AreaBean {
    public String name;

    public AreaBean(String name) {
        this.name = name;
    }
}

接下来将字母和城市按照顺序放入到一个列表中

 List<Object> indexes = new ArrayList<>();
 for (int i = 0; i < 26; i++) {
     List<String> cities = firstCharacter.get(i);
     indexes.add(String.valueOf((char)('A'+i)));
     for (String city : cities) {
         indexes.add(new AreaBean(city));
     }
 }

2、构造适配器
当RecyclerView中不止一种类型布局的时候,我们就需要构造多个ViewHolder对象来适配不同的布局了,这里最重要的是重写 getItemViewType方法,以区分不同的数据类型使用不同的ViewHolder对象。

public class IndexCityAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{
    private static final String TAG = IndexCityAdapter.class.getSimpleName();

    private Context mContext;
    private List<Object> list;

    private final int INDEX = 1;
    private final int CITY = 2;

    public IndexCityAdapter(Context mContext, List<Object> list) {
        this.mContext = mContext;
        this.list = list;
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        if (viewType == INDEX){
            View view = LayoutInflater.from(mContext).inflate(R.layout.city_index,parent,false);
            return new IndexViewHolder(view);
        }else {
            View view = LayoutInflater.from(mContext).inflate(R.layout.city_layout2,parent,false);
            return new CityViewHolder(view);
        }
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof IndexViewHolder){
            ((IndexViewHolder) holder).textView.setText((String) list.get(position));
        }else {
            ((CityViewHolder) holder).textView.setText(((AreaBean)list.get(position)).name);
            holder.itemView.setOnClickListener(v->{
                Intent intent = new Intent(mContext, WeatherMainActivity.class);
                intent.putExtra("cityName",((AreaBean)list.get(position)).name);
                mContext.startActivity(intent);
            });
        }
    }

    @Override
    public int getItemCount() {
        return list.size();
    }

    @Override
    public int getItemViewType(int position) {
        if (list.get(position) instanceof String){
            return INDEX;
        }else {
            return CITY;
        }
    }

	// 索引
    class IndexViewHolder extends RecyclerView.ViewHolder{

        private TextView textView;
        public IndexViewHolder(@NonNull View itemView) {
            super(itemView);
            textView = itemView.findViewById(R.id.tv_city_index);
        }
    }
    // 城市
    class CityViewHolder extends RecyclerView.ViewHolder{

        private TextView textView;
        public CityViewHolder(@NonNull View itemView) {
            super(itemView);
            textView = itemView.findViewById(R.id.tv_city_name);
        }
    }
}

3、布局管理器
这里需要使用GridLayoutManager, 字母和城市网格排序

indexCityAdapter = new IndexCityAdapter(this,indexes);
GridLayoutManager manager = new GridLayoutManager(this,7);
indexCityContainer.setLayoutManager(manager);
indexCityContainer.setAdapter(indexCityAdapter);

到这里基本就完成了,但是一看界面,索引和城市混在一起了,我们希望首字母能够单独 占一行,这时还需要复写Adapter中的onAttachToRecyclerView方法,当遇到字母的时候让它单独占一行就行了。

    @Override
    public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        Log.d(TAG, "onAttachedToRecyclerView: ");
        RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
        if (manager instanceof GridLayoutManager){
            final GridLayoutManager gridLayoutManager = (GridLayoutManager) manager;
            gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                @Override
                public int getSpanSize(int position) {
                    return getItemViewType(position) != INDEX ? 1 : gridLayoutManager.getSpanCount();
                }
            });
        }
    }

给RecyclerView添加上滑动条

RecyclerView 的滑动条是默认隐藏的,我们在布局文件中添加一些属性就可以了

 <android.support.v7.widget.RecyclerView
                android:id="@+id/recycler_view"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:paddingRight="15dp"
                android:scrollbars="vertical"
                android:scrollbarSize="10dp"
                android:fadeScrollbars="false"
                android:scrollbarThumbVertical="@drawable/ic_launcher" 
                android:scrollbarStyle="insideOverlay" />

android:scrollbars=”vertical”是否显示滚动条,它的取值可以是vertical,horizontal或none。
android:fadeScrollbars=”true”(默认参数)是在滑块不滚动时,隐藏
android:fadeScrollbars=”false”是在滑块不滚动时,不隐藏
android:scrollbarThumbVertical=”@drawable/ic_launcher”自定义滑块的背景图
android:scrollbarStyle=”insideOverlay”
insideOverlay:默认值,表示在padding区域内并且覆盖在view上
insideInset:表示在padding区域内并且插入在view后面
outsideOverlay:表示在padding区域外并且覆盖在view上
outsideInset:表示在padding区域外并且插入在view后面

但是现在UI给的图滑动条和RecyclerView是分开的,需要把它们关联起来,滑动条用什么做呢,可以自己定义一个View,但是最方便的方法是使用一个seekbar,这里我使用一个竖直的seekbar,关于竖直的seekbar网上的说法一般就是继承seekbar然后在onDraw里面翻转90度,但是这个方法问题很多,我建议直接使用网上一些现成的库就行了,我使用的是这个

implementation 'com.h6ah4i.android.widget.verticalseekbar:verticalseekbar:1.0.0'

布局使用方式

    <com.h6ah4i.android.widget.verticalseekbar.VerticalSeekBarWrapper
        android:layout_width="4dp"
        android:layout_height="680dp"
        android:layout_marginTop="128dp"
        android:layout_marginLeft="1848dp"
        android:layout_gravity="left">

        <com.h6ah4i.android.widget.verticalseekbar.VerticalSeekBar
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:splitTrack="false"
            android:id="@+id/vs_city_slider"
            android:thumb="@drawable/thumb_city_slider"
            android:thumbOffset="0dp"
            app:seekBarRotation="CW90"
            android:progressDrawable="@drawable/progress_city_slider"/>
    </com.h6ah4i.android.widget.verticalseekbar.VerticalSeekBarWrapper>

它的相关函数和原生的seekbar没有什么区别,使用起来很方便。接下来就是让RecyclerView和Seekbar关联起来,RecyclerView可以给它添加一个滑动监听器,当它滚动的时候计算出竖直长度和偏移量,再将这两个值赋值给seekbar就可以了。

indexCityContainer.addOnScrollListener(new RecyclerView.OnScrollListener() {

   @Override
   public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
       super.onScrolled(recyclerView, dx, dy);
       // 和滑动条关联起来 todo 此处看能否优化,不用每次都计算
       citySlider.setMax(recyclerView.computeVerticalScrollRange());
       int offset = recyclerView.computeVerticalScrollOffset();
       citySlider.setProgress(offset);
   }

});
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,针对你的问题,以下是一个简单的 RecyclerView 使用示例: 1. 在布局文件中添加 RecyclerView ``` <android.support.v7.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" /> ``` 2. 在 Activity 或 Fragment 中获取 RecyclerView 对象并设置 LayoutManager ``` RecyclerView recyclerView = findViewById(R.id.recycler_view); recyclerView.setLayoutManager(new LinearLayoutManager(this)); ``` 3. 创建 Adapter 类并实现 onCreateViewHolder()、onBindViewHolder() 和 getItemCount() 方法 ``` public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> { private List<String> mData; public MyAdapter(List<String> data) { mData = data; } @Override public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_view, parent, false); return new MyViewHolder(view); } @Override public void onBindViewHolder(MyViewHolder holder, int position) { String text = mData.get(position); holder.textView.setText(text); } @Override public int getItemCount() { return mData.size(); } static class MyViewHolder extends RecyclerView.ViewHolder { TextView textView; public MyViewHolder(View itemView) { super(itemView); textView = itemView.findViewById(R.id.text_view); } } } ``` 4. 将 Adapter 实例设置给 RecyclerView ``` MyAdapter myAdapter = new MyAdapter(data); recyclerView.setAdapter(myAdapter); ``` 其中,item_view.xml 是每个 item 的布局文件,可以根据需求自定义。这样就完成了 RecyclerView 的基本使用

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值