最近在做一个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.然后就可以在布局文件中使用自定义的控件了:
<RelativeLayout xmlns:android="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" />
</RelativeLayout>
我去不小心暴露了我的包名(@^_^@)
4.在MainActivity.java中编写代码:
//初始化RecyclerView,设置布局管理器、间距、适配器、数据等
@Override
protected 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<SimpleRecyclerAdapter.ViewHolder>{
private static final String TAG = SimpleRecyclerAdapter.class.getSimpleName();
private LayoutInflater mInflater;
private List<AppBean> mListOfApps;
private int currentPosition = 0;
private Context context;
public SimpleRecyclerAdapter(Context context, List<AppBean> mListOfApps){
mInflater = LayoutInflater.from(context);
this.context = context;
this.mListOfApps = mListOfApps;
}
@SuppressWarnings("unused")
public void setData(List<AppBean> 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;i<count;i++) {
int 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可以用。)