Android(安卓)TV横向滚动网格布局——RecyclerView的使用
最近在做一个Android盒子的项目,主要是Launcher有一个横向滚动的界面。主要使用的是RecyclerView。总结一下。
一、先了解下RecyclerView
RecyclerView是类似于ListView、GridView的一种AdapterView。相比较的优势是使用更加灵活,可以满足实现更多不同的效果。
在我要实现的水平滚动网格布局中就得到了很好的满足。因为使用HorizentalScrollView + GridView的模式会十分复杂,并且焦点、动作的监听会比较混乱。事件冲突处理起来特别麻烦。
二、实现简单例子
记录一下我自己学习的过程。先写了一下RecyclerViewTest的工程。这个工程的主要效果是在页面上显示Android设备上所安装的所有应用。并且点击应用图标可以进入相应的应用。点击菜单键可以卸载该应用。
大概就是这样:
1.先要关联recyclerview的jar包:
dependencies { ... compile 'com.android.support:recyclerview-v7:23.0.1'}
2.然后就可以使用RecyclerView了,先根据需求自定义了一个SimpleRecyclerView.java
public class SimpleRecycleView extends RecyclerView { private static final String TAG = SimpleRecycleView.class.getSimpleName(); // 一个滚动对象 private Scroller mScroller; private int mLastX = 0; public SimpleRecycleView(Context context) { super(context); init(context); } public SimpleRecycleView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public SimpleRecycleView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context); } // 一个初始化方法,传入了一个上下文对象,用来初始化滚动对象 private void init(Context context){ mScroller = new Scroller(context); } // 重写了计算滚动方法 @Override public void computeScroll() { if(mScroller!=null && mScroller.computeScrollOffset()){ scrollBy(mLastX - mScroller.getCurrX(), 0); mLastX = mScroller.getCurrX(); postInvalidate(); } } /** * 调用此方法滚动到目标位置,其中(fx, fy)表示最终要滚到的目标位置的坐标值 * duration表示期间滚动的耗时。 * * @param fx 目标位置的X向坐标值 * @param fy 目标位置的Y向坐标值 * @param duration 滚动到目标位置所消耗的时间毫秒值 */ @SuppressWarnings("unused") public void smoothScrollTo(int fx, int fy,int duration) { int dx = 0; int dy = 0; // 计算变化的位移量 if(fx != 0) { dx = fx - mScroller.getFinalX(); } if(fy!=0) { dy = fy - mScroller.getFinalY(); } Log.i(TAG, "fx:" + fx + ", getFinalX:" + mScroller.getFinalX() + ", dx:" + dx); smoothScrollBy(dx, dy, duration); } /** * 调用此方法设置滚动的相对偏移 */ public void smoothScrollBy(int dx, int dy, int duration) { if(duration > 0) { mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy, duration); } else { // 设置mScroller的滚动偏移量 mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy); } // 重绘整个view,重绘过程会调用到computeScroll()方法。 // 这里必须调用invalidate()才能保证computeScroll()会被调用,否则不一定会刷新界面,看不到滚动效果 invalidate(); } /** * 此方法用来检查自动调节 * * @param position 要检查的位置 */ @SuppressWarnings("unused") public void checkAutoAdjust(int position){ int childCount = getChildCount(); // 获取可视范围内的选项的头尾位置 int firstVisibleItemPosition = ((LinearLayoutManager) getLayoutManager()).findFirstVisibleItemPosition(); int lastVisibleItemPosition = ((LinearLayoutManager) getLayoutManager()).findLastVisibleItemPosition(); Log.d(TAG, "childCount:" + childCount + ", position:" + position + ", firstVisibleItemPosition:" + firstVisibleItemPosition + " lastVisibleItemPosition:" + lastVisibleItemPosition); if(position == (firstVisibleItemPosition + 1) || position == firstVisibleItemPosition){ // 当前位置需要向右平移 leftScrollBy(position, firstVisibleItemPosition); } else if (position == (lastVisibleItemPosition - 1) || position == lastVisibleItemPosition){ // 当前位置需要向左平移 rightScrollBy(position, lastVisibleItemPosition); } } private void leftScrollBy(int position, int firstVisibleItemPosition){ View leftChild = getChildAt(0); if(leftChild != null){ int startLeft = leftChild.getLeft(); int endLeft = (position == firstVisibleItemPosition ? leftChild.getWidth() : 0); Log.d(TAG, "startLeft:" + startLeft + " endLeft" + endLeft); autoAdjustScroll(startLeft, endLeft); } } private void rightScrollBy(int position, int lastVisibleItemPosition){ int childCount = getChildCount(); View rightChild = getChildAt(childCount - 1); if(rightChild != null){ int startRight = rightChild.getRight() - getWidth(); int endRight = (position == lastVisibleItemPosition ? (-1 * rightChild.getWidth()) : 0); Log.d(TAG,"startRight:" + startRight + " endRight:" + endRight); autoAdjustScroll(startRight, endRight); } } /** * * @param start 滑动起始位置 * @param end 滑动结束位置 */ private void autoAdjustScroll(int start, int end){ mLastX = start; mScroller.startScroll(start, 0, end - start, 0); postInvalidate(); } /** * 将指定item平滑移动到整个view的中间位置 * @param position 指定的item的位置 */ public void smoothScrollMaster(int position) { // 这个方法是为了设置Scroller的滚动的,需要根据业务需求,编写算法。 }}
3.然后就可以在布局文件中使用自定义的控件了:
"http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/home_background"> <com.jiuzhou.porter.launcher.widget.SimpleRecycleView android:id="@+id/home_apps" android:layout_marginLeft="@dimen/px_positive_80" android:layout_marginRight="@dimen/px_positive_80" android:layout_width="wrap_content" android:layout_height="wrap_content" android:scrollbars="none" />
我去不小心暴露了我的包名(@^_^@)
4.在MainActivity.java中编写代码:
//初始化RecyclerView,设置布局管理器、间距、适配器、数据等@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main) ... // 1.初始化SimpleRecyclerView mRecyclerView = (SimpleRecycleView) findViewById(R.id.home_apps); // 2.使用自定义的工具类获得设备中安装的APP mListOfApps = LauncherCommonUtils.getAllApk(this); // 3.初始化适配器 SimpleRecyclerAdapter mAdapter = new SimpleRecyclerAdapter(this, mListOfApps); mRecyclerView.setItemAnimator(new DefaultItemAnimator()); // 4.设置布局管理器:瀑布流式 StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(3 , StaggeredGridLayoutManager.HORIZONTAL); // 5.根据需要设置间距等其他内容 mRecyclerView.setLayoutManager(staggeredGridLayoutManager); int right = (int) getResources().getDimension(R.dimen.px_positive_5); int bottom = (int) getResources().getDimension(R.dimen.px_positive_1); RecyclerView.ItemDecoration spacingInPixel = new SpaceItemDecoration(right, bottom); mRecyclerView.addItemDecoration(spacingInPixel); // 6.关联适配器 mRecyclerView.setAdapter(mAdapter);}
- 这里有必要说一下:
关于布局管理器的内容:
RecyclerView的使用时是必须设置布局管理器的。因为不同的布局管理器决定了展现出来的演示是怎样的。
常见的集中布局管理器有LinearLayoutManager、RelativeLayoutManager、GridLayoutManager、StaggeredGridLayoutManager等。
意思一目了然。只提一下StaggeredGridLayoutManager,这个是水平方向的Grid
这里比较重要的是Adapter
5. 关于SimpleRecyclerAdapter
/* * Copyright (c) 2016. Project Launcher * Source SimpleRecyclerAdapter * Author 沈煜 * 此源码及相关文档等附件由 沈煜 编写,作者保留所有权利 * 使用必须注明出处。 * The code and documents is write by the author. All rights are reserved. * Use must indicate the source. * */public class SimpleRecyclerAdapter extends RecyclerView.Adapter{ private static final String TAG = SimpleRecyclerAdapter.class.getSimpleName(); private LayoutInflater mInflater; private List mListOfApps; private int currentPosition = 0; private Context context; public SimpleRecyclerAdapter(Context context, List mListOfApps){ mInflater = LayoutInflater.from(context); this.context = context; this.mListOfApps = mListOfApps; } @SuppressWarnings("unused") public void setData(List mListOfApps){ this.mListOfApps = mListOfApps; } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = mInflater.inflate(R.layout.item_grid_apps, parent, false); ViewHolder vh = new ViewHolder(view); vh.mImageView = (ImageView) view.findViewById(R.id.home_grid_item_icon); vh.mTextView = (TextView) view.findViewById(R.id.home_grid_item_name); return vh; } private View mOldFocus; @Override public void onBindViewHolder(final ViewHolder holder, final int position) { holder.mImageView.setImageDrawable(mListOfApps.get(position).getAppIcon()); holder.mTextView.setText(mListOfApps.get(position).getAppName()); // 设置itemView可以获得焦点 holder.itemView.setFocusable(true); holder.itemView.setTag(position); holder.itemView.setOnFocusChangeListener(new View.OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { if (hasFocus) { currentPosition = (int) holder.itemView.getTag(); mOnItemSelectListener.onItemSelect(holder.itemView, currentPosition); if (v != mOldFocus) { View vb = v.findViewById(R.id.home_back_2); GradientDrawable gd = (GradientDrawable) vb.getBackground(); int width = (int) context.getResources().getDimension(R.dimen.px_positive_3); int color = context.getResources().getColor(R.color.color0); int radius = (int) context.getResources().getDimension(R.dimen.px_positive_25); gd.setStroke(width, color); gd.setCornerRadius(radius); if (mOldFocus != null) { View ovb = mOldFocus.findViewById(R.id.home_back_2); GradientDrawable ogd = (GradientDrawable) ovb.getBackground(); ogd.setStroke(0, Color.parseColor("#00000000")); } } mOldFocus = v; } else { if (v != null) { View ovb2 = v.findViewById(R.id.home_back_2); GradientDrawable ogd2 = (GradientDrawable) ovb2.getBackground(); ogd2.setStroke(0, Color.parseColor("#00000000")); } } } }); holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mOnItemClickListener.onItemClick(v, currentPosition); } }); holder.itemView.setOnKeyListener(new View.OnKeyListener() { @Override public boolean onKey(View v, int keyCode, KeyEvent event) { mOnItemKeyListener.OnItemKey(v, keyCode, event, currentPosition); return false; } }); } @Override public int getItemCount() { return mListOfApps.size(); } private int index = 0; class ViewHolder extends RecyclerView.ViewHolder{ ImageView mImageView; TextView mTextView; ViewHolder(View itemView) { super(itemView); ImageView back2 = (ImageView) itemView.findViewById(R.id.home_back_2); GradientDrawable background = (GradientDrawable) back2.getBackground(); TypedArray ta = context.getResources().obtainTypedArray(R.array.appBackgroundColors); int count = ta.length(); int [] colorsArray = new int[count]; for (int i=0;iint resId = ta.getResourceId(i, -1); colorsArray[i] = resId; } /*Random random = new Random(); int index = random.nextInt(count); while (oldIndex == index) { index = random.nextInt(); } oldIndex = index;*/ background.setColor(context.getResources().getColor(colorsArray[index])); if (index < count - 1) { index += 1; } else { index = 0; } ta.recycle(); } } private OnItemSelectListener mOnItemSelectListener; private OnItemClickListener mOnItemClickListener; private OnItemLongClickListener mOnItemLongClickListener; private OnItemKeyListener mOnItemKeyListener; public interface OnItemSelectListener { void onItemSelect(View view, int position); } public interface OnItemClickListener { void onItemClick(View view, int position); } public interface OnItemLongClickListener { void onItemLongClick(View view, int position); } public interface OnItemKeyListener { void OnItemKey(View view, int keyCode, KeyEvent event, int position); } public void setOnItemSelectListener(OnItemSelectListener listener){ mOnItemSelectListener = listener; } public void setOnItemClickListener(OnItemClickListener mOnItemClickListener) { this.mOnItemClickListener = mOnItemClickListener; } public void setOnItemLongClickListener(OnItemLongClickListener mOnItemLongClickListener) { this.mOnItemLongClickListener = mOnItemLongClickListener; } public void setOnItemKeyListener(OnItemKeyListener mOnItemKeyListener) { this.mOnItemKeyListener = mOnItemKeyListener; }}
最后有一个重要的地方就是Adapter中写了许多监听器。这个是RecyclerView特有的,因为RecyclerView没有监听器!!!(简直了,不能忍好嘛。所以要在Adapter中自己定义监听。因为你监听的是其中的itemView,当然了也可以去RecyclerView里面写诸如OnItemClick这样的监听器,会麻烦一点。不过那样封装起来比较牛。去GitHub上应该有这样的jar可以用。)
差不多了吧
————————-不怎么华丽的分割线—————–
MDZZ,这个里面的焦点控制忘了写!!!厉害了我的哥,下期详解。
更多相关文章
- 第17天 Android(安卓)Touch事件学习 4 获取手指触摸位置
- android LinearLayout布局嵌套覆盖问题
- 修正AppCompatSpinner弹出框位置问题
- Android桌面组件App Widget开发三步走
- Android学习整理 -1- Fragment 学习
- Android(安卓)实现自定义的卫星式菜单(弧形菜单)View
- Android(安卓)SDK Manager无法显示可供下载的未安装SDK解决方案
- Android(安卓)Layout 布局
- Android(Xamarin)之旅(三)