自定义RecycleView实现TV应用上的item焦点获取以及设置当前选中的item居中

来源:互联网 发布:服务器上的java 编辑:程序博客网 时间:2024/06/08 08:36

转载来自:http://blog.csdn.net/anhenzhufeng/article/details/50209117

github地址:https://github.com/tianyasifan/MyRecyclerView


RecycleView是个强大的控件,能代替ListView,GridView,还能实现瀑布流,还能实现横向ListView,只需要一句代码就能使纵向ListView变成横向的(主要实现就在布局管理器的选择上了)。

其功能用法这里不再赘述,有很多资料可供大家学习。


目前所在TV应用,经常使用到横向的列表。实现横向列表也有多种方式,Gallery,horizontalscrollview等,这些控件或多或少都存在这样那样的问题,感觉使用起来不是很方便。

既然RecycleView(后文使用rv代替)出现了,其又能支持横向ListView,我们还有不使用它的理由吗!


下面我们就来一步一步实现。

首先,我们得弄出一个横向的ListView来。这个简单,看代码

[java] view plain copy
  1. //线性布局  
  2.        final LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);  
  3.        linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);  
  4.        mRecyclerView.setLayoutManager(linearLayoutManager);  
使用一个线性布局管理器,其方向设置横向,这样一个横向ListView样式的列表就出来了。

样式有了,我们就来绑定数据源了。这里我使用了一组图片作为rv的item数据,先来初始化数组

[java] view plain copy
  1. private void initDatas()  
  2.     {  
  3.         mDatas = new ArrayList<Integer>(Arrays.asList(R.mipmap.osd_blue_hl,  
  4.                 R.mipmap.ic_launcher,R.mipmap.ic_launcher, R.mipmap.ic_launcher, R.mipmap.ic_launcher,  
  5.                 R.mipmap.ic_launcher,R.mipmap.ic_launcher,R.mipmap.ic_launcher,R.mipmap.ic_launcher,R.mipmap.ic_launcher,  
  6.                 R.mipmap.ic_launcher,R.mipmap.ic_launcher,R.mipmap.ic_launcher,R.mipmap.ic_launcher,R.mipmap.osd_hd_hl));  
  7.     }  
数据有了,我们就需要使用适配器绑定数据了。还好,rv为我们提供了RecycleView.Adapter,并且封装了RecycleView.ViewHolder。我们不必为item的回收废脑子了。

具体使用和BaseAdapter一样,我们需要继承RecycleView.Adapter实现具体的逻辑。以下是adapter的实现

[java] view plain copy
  1. package com.example.txt.myrecyclerview;  
  2.   
  3. import android.content.Context;  
  4. import android.nfc.Tag;  
  5. import android.support.v7.widget.RecyclerView;  
  6. import android.util.Log;  
  7. import android.view.LayoutInflater;  
  8. import android.view.View;  
  9. import android.view.ViewGroup;  
  10. import android.widget.ImageView;  
  11. import android.widget.TextView;  
  12.   
  13. import org.w3c.dom.Text;  
  14.   
  15. import java.util.ArrayList;  
  16. import java.util.List;  
  17. import java.util.Random;  
  18.   
  19. /** 
  20.  * Created by txt on 2015/11/11. 
  21.  */  
  22. public class GalleryAdapter extends RecyclerView.Adapter<GalleryAdapter.ViewHolder>{  
  23.     private LayoutInflater mInflater;  
  24.     private List<Integer> mDatas;  
  25.     private List<Integer> heights;  
  26.     private int currentPosition;  
  27.   
  28.     public interface OnItemClickListener {  
  29.         void onItemClick(View view, int position);  
  30.         void onItemLongClick(View view,int position);  
  31.     }  
  32.   
  33.     public interface OnItemSelectListener{  
  34.         void onItemSelect(View view,int position);  
  35.     }  
  36.   
  37.     private OnItemClickListener mListener;  
  38.     private OnItemSelectListener mSelectListener;  
  39.   
  40.     public void setOnItemSelectListener(OnItemSelectListener listener){  
  41.         mSelectListener = listener;  
  42.     }  
  43.   
  44.     public void setOnItemClickListener(OnItemClickListener listener){  
  45.         mListener = listener;  
  46.     }  
  47.   
  48.     public GalleryAdapter(Context context,List<Integer> datas){  
  49.         mInflater = LayoutInflater.from(context);  
  50.         mDatas = datas;  
  51.         getRandomHeight(mDatas.size());  
  52.     }  
  53.   
  54.     public void setDatas(List datas){  
  55.         mDatas = datas;  
  56.     }  
  57.   
  58.     @Override  
  59.     public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {  
  60.         View view = mInflater.inflate(R.layout.item_rv, parent, false);  
  61.         ViewHolder holder = new ViewHolder(view);  
  62.         holder.mImg = (ImageView) view.findViewById(R.id.id_index_gallery_item_image);  
  63.         holder.mTxt = (TextView)view.findViewById(R.id.id_index_gallery_item_text);  
  64.         return holder;  
  65.     }  
  66.   
  67.     @Override  
  68.     public void onBindViewHolder(final ViewHolder holder, final int position) {  
  69.         ViewGroup.LayoutParams params = holder.itemView.getLayoutParams();  
  70.         params.height = heights.get(position%mDatas.size());  
  71.         holder.itemView.setLayoutParams(params);  
  72.         holder.mImg.setImageResource(mDatas.get(position % mDatas.size()));  
  73.         holder.mTxt.setText(""+position);  
  74.   
  75.         holder.itemView.setFocusable(true);  
  76.         holder.itemView.setTag(position);  
  77.         holder.itemView.setOnFocusChangeListener(new View.OnFocusChangeListener() {  
  78.             @Override  
  79.             public void onFocusChange(View v, boolean hasFocus) {  
  80.                 Log.i("adapter""hasfocus:" + position + "--" + hasFocus);  
  81.                 if(hasFocus){  
  82.                     currentPosition = (int)holder.itemView.getTag();  
  83.                     mSelectListener.onItemSelect(holder.itemView,currentPosition);  
  84.                 }  
  85.             }  
  86.         });  
  87.         if(mListener!=null){  
  88.             holder.itemView.setOnClickListener(new View.OnClickListener() {  
  89.                 @Override  
  90.                 public void onClick(View v) {  
  91.                     mListener.onItemClick(v,holder.getLayoutPosition());  
  92.                 }  
  93.             });  
  94.             holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {  
  95.                 @Override  
  96.                 public boolean onLongClick(View v) {  
  97.                     mListener.onItemLongClick(v,holder.getLayoutPosition());  
  98.                     return true;  
  99.                 }  
  100.             });  
  101.         }  
  102.     }  
  103.   
  104.     @Override  
  105.     public int getItemCount() {  
  106. //        return Integer.MAX_VALUE;  
  107.         return mDatas.size();  
  108.     }  
  109.   
  110.   
  111.     private void getRandomHeight(int size){  
  112.         heights = new ArrayList<>();  
  113.         for(int i=0;i<size;i++){  
  114.             heights.add((int)(200+Math.random()*400));  
  115.         }  
  116.     }  
  117.   
  118.     public static class ViewHolder extends RecyclerView.ViewHolder{  
  119.         ImageView mImg;  
  120.         TextView mTxt;  
  121.         public ViewHolder(View itemView) {  
  122.             super(itemView);  
  123.         }  
  124.     }  
  125. }  
实现过程很简单,构造行数里面得到数据源,onCreateViewHolder方法里面实例化ViewHolder对象,onBindViewHolder方法里面进行具体的数据绑定。细心的朋友会发现,RecycleView并没有提供onItemClickListener和onItemSelectedListener方法,所以我们需要自己来实现这两个监听。

item点击事件请移步鸿洋大神的Android RecyclerView 使用完全解析 体验艺术般的控件

这里我主要说下选择事件的处理。刚开始的时候,有点懵,没有头绪,打算仿照ListView的实现方式,看了下源码,没搞懂,遂放弃。

由于我的rv是要用到TV项目中的,TV项目中一个重要的事件就是焦点事件,常规ListView获得焦点后,其子item就能获取到焦点,不用刻意的去设置,遥控器切换上下键的时候,焦点就能移动到相应item上。起初,我把rv显示的获得焦点,但是子item并未获取到焦点(item获得焦点后,背景图片会改变),后来参考了一篇文章,需要手动设置让子item能获得焦点(具体可猛戳这里),这样一来焦点就在item上了,使用左右键,焦点就在item上移动了,并且焦点移动到当前可见列表的边缘item时,在选择下一个(或上一个)item时,整个item集合会往前(或往后)移动(窃喜,还好控件本身实现了这个功能,否则还得手动设置列表的滚动呢)。

焦点的问题就这样解决了,间接的实现了类似ListView的setSelection方法(rv没有setSelection方法,但是linearLayoutManager有scrollToPosition方法,感觉不太好用,没测试,貌似好多操作都移到了布局管理来实现)。


到这里我们使用rv实现横向listview的功能基本上算是实现了(TV上获取焦点)。


目前我们还有一个需求,就是当前选中的item希望它能一直保持在列表的中间位置,如下图所示


当焦点在第0、1个item时保持不变,当焦点移动到第2个item时,把第2个item挪到列表中间,如下


要实现这个效果,实际上需要控制当前选中的item向左或向右滚动的距离。这样就用到了Scroller这个对象,具体用法可参考

http://ipjmc.iteye.com/blog/1615828

http://blog.csdn.net/c_weibin/article/details/7438323

也可以自行百度。这里假设你已经了解了这个类。我们继续。


我们先来实现一个自定义的RecycleView。

[java] view plain copy
  1. public CustomRecycleView(Context context) {  
  2.        super(context);  
  3.        init(context);  
  4.    }  
  5.   
  6.    public CustomRecycleView(Context context, AttributeSet attrs) {  
  7.        super(context, attrs);  
  8.        init(context);  
  9.    }  
  10.   
  11.    public CustomRecycleView(Context context, AttributeSet attrs, int defStyle) {  
  12.        super(context, attrs, defStyle);  
  13.        init(context);  
  14.    }  
  15.   
  16.    private void init(Context context){  
  17.        mScroller = new Scroller(context);  
  18.    }  
在构造函数里面初始化一个Scroller对象。复写computeScroll方法,这个方法在调用postInvalidate的时候会执行到
[java] view plain copy
  1. @Override  
  2.     public void computeScroll() {  
  3.         super.computeScroll();  
  4.         //computeScrollOffset返回true表示滚动还在继续,持续时间应该就是startScroll设置的时间  
  5.         if(mScroller!=null && mScroller.computeScrollOffset()){  
  6.             Log.d(TAG, "getCurrX = " + mScroller.getCurrX());  
  7.             scrollBy(mLastx - mScroller.getCurrX(), 0);  
  8.             mLastx = mScroller.getCurrX();  
  9.             postInvalidate();//让系统继续重绘,则会继续重复执行computeScroll  
  10.         }  
  11.     }  
在这个方法里面计算滚动的偏移量,调用scrollBy方法执行滚动事件

下面这个方法就是具体的设置了
[java] view plain copy
  1. /** 
  2.      * 将指定item平滑移动到整个view的中间位置 
  3.      * @param position 
  4.      */  
  5.     public void smoothToCenter(int position){  
  6.         int parentWidth = getWidth();//获取父视图的宽度  
  7.         int childCount = getChildCount();//获取当前视图可见子view的总数  
  8.         //获取可视范围内的选项的头尾位置  
  9.         int firstvisiableposition = ((LinearLayoutManager) getLayoutManager()).findFirstVisibleItemPosition();  
  10.         int lastvisiableposition = ((LinearLayoutManager) getLayoutManager()).findLastVisibleItemPosition();  
  11.         int count = ((LinearLayoutManager)getLayoutManager()).getItemCount();//获取item总数  
  12.         Log.i(TAG,"count:"+count);  
  13.         mTargetPos = Math.max(0, Math.min(count - 1, position));//获取目标item的位置(参考listview中的smoothScrollToPosition方法)  
  14.         Log.i(TAG, "firstposition:" + firstvisiableposition + "   lastposition:" + lastvisiableposition + "   position:" + position+  
  15.                 "   mTargetPos:"+mTargetPos);  
  16.         View targetChild = getChildAt(mTargetPos-firstvisiableposition);//获取目标item在当前可见视图item集合中的位置  
  17.         View firstChild = getChildAt(0);//当前可见视图集合中的最左view  
  18.         View lastChild = getChildAt(childCount-1);//当前可见视图集合中的最右view  
  19.         Log.i(TAG,"first-->left:"+firstChild.getLeft()+"   right:"+firstChild.getRight());  
  20.         Log.i(TAG, "last-->left:" + lastChild.getLeft() + "   right:" + lastChild.getRight());  
  21.         int childLeftPx = targetChild.getLeft();//子view相对于父view的左边距  
  22.         int childRightPx = targetChild.getRight();//子view相对于父view的右边距  
  23.         Log.i(TAG, "target-->left:" + targetChild.getLeft() + "   right:" + targetChild.getRight());  
  24.   
  25.   
  26.         int childWidth = targetChild.getWidth();  
  27.         int centerLeft = parentWidth/2-childWidth/2;//计算子view居中后相对于父view的左边距  
  28.         int centerRight = parentWidth/2+childWidth/2;//计算子view居中后相对于父view的右边距  
  29.         Log.i(TAG,"rv width:"+parentWidth+"   item width:"+childWidth+"   centerleft:"+centerLeft+"   centerRight:"+centerRight);  
  30.         if(childLeftPx>centerLeft){//子view左边距比居中view大(说明子view靠父view的右边,此时需要把子view向左平移  
  31.             //平移的起始位置就是子view的左边距,平移的距离就是两者之差  
  32.             mLastx = childLeftPx;  
  33.             mScroller.startScroll(childLeftPx,0,centerLeft-childLeftPx,0,600);//600为移动时长,可自行设定  
  34.             postInvalidate();  
  35.         }else if(childRightPx<centerRight){  
  36.             mLastx = childRightPx;  
  37.             mScroller.startScroll(childRightPx,0,centerRight-childRightPx,0,600);  
  38.             postInvalidate();  
  39.         }  
  40.   
  41.   
  42.     }  
注释非常清楚了,主要是通过传递过来的position计算出当前可见视图中该position 对应的item相对父布局的左右边距
[java] view plain copy
  1. View targetChild = getChildAt(mTargetPos-firstvisiableposition);//获取目标item在当前可见视图item集合中的位置  

假定当前item在布局中间,获得该子item的左右目标边距

[java] view plain copy
  1. int centerLeft = parentWidth/2-childWidth/2;//计算子view居中后相对于父view的左边距  
  2.         int centerRight = parentWidth/2+childWidth/2;//计算子view居中后相对于父view的右边距  
和实际的item的左右边距做比较
[java] view plain copy
  1. if(childLeftPx>centerLeft){//子view左边距比居中view大(说明子view靠父view的右边,此时需要把子view向左平移  
  2.             //平移的起始位置就是子view的左边距,平移的距离就是两者之差  
  3.             mLastx = childLeftPx;  
  4.             mScroller.startScroll(childLeftPx,0,centerLeft-childLeftPx,0,600);//600为移动时长,可自行设定  
  5.             postInvalidate();  
  6.         }else if(childRightPx<centerRight){  
  7.             mLastx = childRightPx;  
  8.             mScroller.startScroll(childRightPx,0,centerRight-childRightPx,0,600);  
  9.             postInvalidate();  
  10.         }  
根据比较结果,进行滚动设置
[java] view plain copy
  1. mScroller.startScroll(childRightPx,0,centerRight-childRightPx,0,600);  

这样设置之后就可以实现当焦点变换时,当前选中的item能平滑的移动到屏幕中间位置。

在activity中如下调用,其中onItemSelect方法由子item的焦点变化触发,详见上面的adapter类

[java] view plain copy
  1. mAdapter.setOnItemSelectListener(new GalleryAdapter.OnItemSelectListener() {  
  2.             @Override  
  3.             public void onItemSelect(View view, int position) {  
  4. //                linearLayoutManager.scrollToPositionWithOffset(position,350);  
  5.                 mRecyclerView.smoothToCenter(position);  
  6.             }  
  7.         }); 




阅读全文
0 0
原创粉丝点击