自学安卓也有一年的时间了,与代码相伴的日子里,苦乐共存。能坚持到现在确实已见到了“往日所未曾见证的风采”。今2018年4月2日,决定用一个案例:Unit_Common,把安卓基础的知识进行串联,形成模块化的总结性测试案例,一方面是对自己的总结,在总结中温故知新。另一方面通过博客,和众多开发爱好者进行知识分享,希望可以帮助到一些新入安卓的人。
本项目通过一个RecyclerView进行模块的划分,点击item进入相应模块。所以本篇先用RecyclerView将整个案例的框架搭建出来,这篇对新手可能比较难以理解,但并不影响学习后面的简单知识。
为了方便查看将每个模块的名称和图片展示在RecyclerView的item上:
1 package top.toly.www.unit_common.bean; 2 3 /** 4 * 作者:张风捷特烈 5 * 时间:2018/4/10:14:55 6 * 邮箱:1981462002@qq.com 7 * 说明:每个界面的bean对象 图片+名称 8 */ 9 public class ItemBean { 10 11 private String name; 12 private int ResId; 13 14 public ItemBean(String name, int resId) { 15 this.name = name; 16 ResId = resId; 17 } 18 19 public String getName() { 20 return name; 21 } 22 23 public void setName(String name) { 24 this.name = name; 25 } 26 27 public int getResId() { 28 return ResId; 29 } 30 31 public void setResId(int resId) { 32 ResId = resId; 33 } 34 }
Activity_Home_RV 我们需要关注的是OnRvItemClick方法:通过位置打开模块。 setItemsData方法设置数据
1 注:笔者为避免寻找id的麻烦,使用了ButterKnife 2 依赖:implementation 'com.jakewharton:butterknife:7.0.1' 3 混淆:#butterknife 4 -keep class butterknife.** { *; } 5 -dontwarn butterknife.internal.** 6 -keep class **$$ViewBinder { *; } 7 -keepclasseswithmembernames class * { 8 @butterknife.* <fields>; 9 } 10 -keepclasseswithmembernames class * { 11 @butterknife.* <methods>; 12 }
1 package top.toly.www.unit_common.home; 2 3 import android.content.Intent; 4 import android.os.Bundle; 5 import android.support.v7.app.AppCompatActivity; 6 import android.support.v7.widget.RecyclerView; 7 import android.view.View; 8 9 import java.util.ArrayList; 10 import java.util.List; 11 12 import butterknife.Bind; 13 import butterknife.ButterKnife; 14 import top.toly.www.unit_common.R; 15 import top.toly.www.unit_common.activity.MainActivity; 16 import top.toly.www.unit_common.bean.ItemBean; 17 import utils.ev.recyclerview.MyRVAdapter; 18 import utils.ev.recyclerview.MyRVHolder; 19 import utils.ui.UIUtils; 20 21 public class Activity_Home_RV extends AppCompatActivity { 22 23 @Bind(R.id.recyclerview) 24 RecyclerView mRecyclerview; 25 private List<ItemBean> mItems;//itemBean的集合 26 27 @Override 28 protected void onCreate(Bundle savedInstanceState) { 29 super.onCreate(savedInstanceState); 30 setContentView(R.layout.activity_home_rv); 31 ButterKnife.bind(this); 32 33 setItemsData();//为item设置数据 34 initRV();//初始化RecyclerView 35 36 } 37 38 private void initRV() { 39 // 注:笔者已对RecyclerView进行封装,以下几行就搞定RecyclerView的简单使用,封装代码见下 40 UIUtils.setStyle4RV(mRecyclerview, 3, UIUtils.GRIDVIEW, this);//设置类型 41 MyRVAdapter<ItemBean> rvAdapter = new MyRVAdapter<ItemBean>(mItems, R.layout.rv_item_home) { 42 @Override 43 public void setDatas(MyRVHolder holder, ItemBean data, int position) { 44 holder.setText(R.id.tv_title, data.getName()) 45 .setImageViewRes(R.id.iv_icon, data.getResId()); 46 } 47 }; 48 49 mRecyclerview.setAdapter(rvAdapter); 50 rvAdapter.setOnRvItemClickListener(new MyRVAdapter.OnRvItemClickListener() { 51 @Override 52 public void OnRvItemClick(View v, int pos) { 53 switch (pos) { 54 case 0: 55 startActivity(new Intent(Activity_Home_RV.this, MainActivity.class)); 56 break; 57 } 58 } 59 }); 60 } 61 62 /** 63 * 为item设置数据 64 * 65 * @return 66 */ 67 68 public List<ItemBean> setItemsData() { 69 mItems = new ArrayList<>(); 70 mItems.add(new ItemBean("test", R.drawable.ic_launcher_background)); 71 mItems.add(new ItemBean("test1", R.drawable.ic_launcher_background)); 72 return mItems; 73 } 74 75 }
布局:
activity_home_rv.xml:
1 <?xml version="1.0" encoding="utf-8"?> 2 <RelativeLayout 3 xmlns:android="http://schemas.android.com/apk/res/android" 4 xmlns:tools="http://schemas.android.com/tools" 5 android:layout_width="match_parent" 6 android:layout_height="match_parent"> 7 8 <android.support.v7.widget.RecyclerView 9 android:id="@+id/recyclerview" 10 android:layout_width="match_parent" 11 android:layout_height="wrap_content"/> 12 13 </RelativeLayout>
rv_item_home.xml:
1 <?xml version="1.0" encoding="utf-8"?> 2 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="wrap_content" 5 android:padding="5dp"> 6 7 <ImageView 8 android:id="@+id/iv_icon" 9 android:layout_width="50dp" 10 android:layout_height="50dp" 11 android:src="@drawable/ic_launcher_background" /> 12 13 <TextView 14 android:id="@+id/tv_title" 15 android:layout_width="match_parent" 16 android:layout_height="wrap_content" 17 android:layout_marginLeft="3dp" 18 android:layout_toRightOf="@+id/iv_icon" 19 android:layout_centerInParent="true" 20 android:text="Content" 21 android:textAllCaps="false" 22 android:textColor="#000000" /> 23 </RelativeLayout>
效果如下:随着mItems数量增加,RecyclerView也会增加
下面是笔者封装的代码:虽然比较多,但拷贝进去就能用。好了,综述就到这里。
对RecyclerView进行封装:两个类MyRVHolder和MyRVAdapter
package utils.ev.recyclerview; import android.graphics.Bitmap; import android.support.v7.widget.RecyclerView; import android.util.SparseArray; import android.view.View; import android.widget.ImageView; import android.widget.TextView; import com.bumptech.glide.Glide; import com.bumptech.glide.load.engine.DiskCacheStrategy; import utils.ui.UIUtils; /** * 作者:张风捷特烈 * 时间:2018/4/10:14:22 * 邮箱:1981462002@qq.com * 说明:View的持有人 */ public class MyRVHolder extends RecyclerView.ViewHolder { //键值对中键是int类型使用SparseArray比map更好 private SparseArray<View> mViews;//持有的所有View集合 private View mItemView;//Item的 public MyRVHolder(View itemView) { super(itemView); mItemView = itemView; mViews = new SparseArray<>(); } /** * 获取pos * * @return */ public int getPos() { return this.getLayoutPosition(); } /** * 通过viewId获取控件 * * @param viewId * @param <T> * @return */ public <T extends View> T getView(int viewId) { View view = mViews.get(viewId); if (view == null) { view = mItemView.findViewById(viewId); mViews.put(viewId, view);//以id为键,view为值 } return (T) view; } public View getItemView() { return mItemView; } /** * 设置item背景颜色 */ public MyRVHolder setColor(int color) { mItemView.setBackgroundColor(color); return this; } /** * 设置TextView文本方法 * * @param viewId * @param text * @return */ public MyRVHolder setText(int viewId, String text) { TextView view = getView(viewId); view.setText(text); return this; } /** * 通过资源id设置ImageView图片 * @param viewId * @param resId * @return */ public MyRVHolder setImageViewRes(int viewId, int resId) { ImageView view = getView(viewId); view.setImageResource(resId); return this; } /** * 通过Bitmap设置ImageView图片 * @param viewId * @param bitmap * @return */ public MyRVHolder setImageViewBitmap(int viewId, Bitmap bitmap) { ImageView view = getView(viewId); view.setImageBitmap(bitmap); return this; } /** * 通过url设置图片 * @param viewId * @param url * @return */ public MyRVHolder setImageViewUrl(int viewId, String url) { ImageView view = getView(viewId); //此处使用Glide进行Url类型图片的加载,如果未添加Glide依赖会报错 //依赖: implementation 'com.github.bumptech.glide:glide:3.7.0' Glide.with(UIUtils.getContext()) .load(url) .skipMemoryCache(false) .diskCacheStrategy(DiskCacheStrategy.SOURCE) .into(view); return this; } /////////////////////可继续拓展完善,添加更多方法////////////////////// }
package utils.ev.recyclerview; import android.support.v7.widget.RecyclerView; import android.view.View; import android.view.ViewGroup; import java.util.List; import utils.ui.UIUtils; /** * 作者:张风捷特烈 * 时间:2018/4/10:14:28 * 邮箱:1981462002@qq.com * 说明:RecyclerView的Adapter封装类 */ public abstract class MyRVAdapter<T> extends RecyclerView.Adapter<MyRVHolder> { protected List<T> mDatas; protected int mItemId; private View mItemView; public MyRVAdapter(List<T> datas, int itemId) { mDatas = datas; mItemId = itemId; } @Override public MyRVHolder onCreateViewHolder(ViewGroup parent, int viewType) { mItemView = UIUtils.inflate(mItemId); final MyRVHolder myRVHolder = new MyRVHolder(mItemView); //点击事件方式 mItemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) {//使用回调实现点击监听 if (mOnRvItemClickListener != null) { mOnRvItemClickListener.OnRvItemClick(v, myRVHolder.getPos()); } } }); return myRVHolder; } @Override public void onBindViewHolder(MyRVHolder holder, int position) { setDatas(holder, mDatas.get(position), position); } @Override public int getItemCount() { return mDatas.size(); } /** * 抽象方法,通过holder可对各控件进行操作 * * @param holder View的持有人 * @param data 数据 * @param position 点击位置 */ public abstract void setDatas(MyRVHolder holder, T data, int position); /////////////////////////////////////////////////////////// /** * 添加item * * @param i * @param aNew */ public void addData(int i, T aNew) { mDatas.add(i, aNew); notifyItemInserted(i);//刷新数据 } /** * 删除item * * @param i */ public void deleteData(int i) { mDatas.remove(i); notifyItemRemoved(i);//刷新数据 } public View getItemView() { return mItemView; } /////////////////为RecyclerView设置点击监听接口///////////////////////// /** * 为RecyclerView设置点击监听接口 */ public interface OnRvItemClickListener { void OnRvItemClick(View v, int pos);//item被点击的时候回调方法 } /** * 声明监听器接口对象 */ private OnRvItemClickListener mOnRvItemClickListener; /** * 设置RecyclerView某个的监听方法 * * @param onRvItemClickListener */ public void setOnRvItemClickListener(OnRvItemClickListener onRvItemClickListener) { mOnRvItemClickListener = onRvItemClickListener; } }
这样可以使用了,不过为了添加分割线,还有免去写一些初始化的设置方法,把其封装在我的UiUtils中,静态方法如下:
////////////////////////设置RecyclerView///////////////////////////////////////// public static final int GRIDVIEW = 0; public static final int LISTVIEW = 1; public static final int PULL = 2; /** * * @param rv RecyclerView * @param count count 数量 LISTVIEW可随意 * @param style 模式 GRIDVIEW LISTVIEW PULL * @param ctx 上下文 */ public static GridLayoutManager setStyle4RV(RecyclerView rv, int count, int style,Context ctx) { switch (style) { case GRIDVIEW://GridView类型 rv.addItemDecoration(new MyDividerItemDecoration(ctx));//设置分割线 GridLayoutManager gridLayoutManager = new GridLayoutManager(getContext(), count, GridLayoutManager.VERTICAL, false); rv.setLayoutManager(gridLayoutManager); return gridLayoutManager; case LISTVIEW://ListView类型 rv.addItemDecoration(new SampleDivider(ctx)); rv.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false)); return null; case PULL://瀑布流类型 rv.addItemDecoration(new MyDividerItemDecoration(ctx)); rv.setLayoutManager(new StaggeredGridLayoutManager(count, StaggeredGridLayoutManager.VERTICAL)); return null; } return null; }
MyDividerItemDecoration 分割线:
package utils.ev.recyclerview; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.OrientationHelper; import android.support.v7.widget.RecyclerView; import android.view.View; public class MyDividerItemDecoration extends RecyclerView.ItemDecoration { private static final int[] ATTRS = new int[]{ android.R.attr.listDivider }; /** * 用于绘制间隔样式 */ private Drawable mDivider; public MyDividerItemDecoration(Context context) { // 获取默认主题的属性 final TypedArray a = context.obtainStyledAttributes(ATTRS); mDivider = a.getDrawable(0); a.recycle(); } @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { // 绘制间隔,每一个item,绘制右边和下方间隔样式 int childCount = parent.getChildCount(); int spanCount = ((GridLayoutManager)parent.getLayoutManager()).getSpanCount(); int orientation = ((GridLayoutManager)parent.getLayoutManager()).getOrientation(); boolean isDrawHorizontalDivider = true; boolean isDrawVerticalDivider = true; int extra = childCount % spanCount; extra = extra == 0 ? spanCount : extra; for(int i = 0; i < childCount; i++) { isDrawVerticalDivider = true; isDrawHorizontalDivider = true; // 如果是竖直方向,最右边一列不绘制竖直方向的间隔 if(orientation == OrientationHelper.VERTICAL && (i + 1) % spanCount == 0) { isDrawVerticalDivider = false; } // 如果是竖直方向,最后一行不绘制水平方向间隔 if(orientation == OrientationHelper.VERTICAL && i >= childCount - extra) { isDrawHorizontalDivider = false; } // 如果是水平方向,最下面一行不绘制水平方向的间隔 if(orientation == OrientationHelper.HORIZONTAL && (i + 1) % spanCount == 0) { isDrawHorizontalDivider = false; } // 如果是水平方向,最后一列不绘制竖直方向间隔 if(orientation == OrientationHelper.HORIZONTAL && i >= childCount - extra) { isDrawVerticalDivider = false; } if(isDrawHorizontalDivider) { drawHorizontalDivider(c, parent, i); } if(isDrawVerticalDivider) { drawVerticalDivider(c, parent, i); } } } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { int spanCount = ((GridLayoutManager) parent.getLayoutManager()).getSpanCount(); int orientation = ((GridLayoutManager)parent.getLayoutManager()).getOrientation(); int position = parent.getChildLayoutPosition(view); if(orientation == OrientationHelper.VERTICAL && (position + 1) % spanCount == 0) { outRect.set(0, 0, 0, mDivider.getIntrinsicHeight()); return; } if(orientation == OrientationHelper.HORIZONTAL && (position + 1) % spanCount == 0) { outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0); return; } outRect.set(0, 0, mDivider.getIntrinsicWidth(), mDivider.getIntrinsicHeight()); } /** * 绘制竖直间隔线 * * @param canvas * @param parent * 父布局,RecyclerView * @param position * irem在父布局中所在的位置 */ private void drawVerticalDivider(Canvas canvas, RecyclerView parent, int position) { final View child = parent.getChildAt(position); final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child .getLayoutParams(); final int top = child.getTop() - params.topMargin; final int bottom = child.getBottom() + params.bottomMargin + mDivider.getIntrinsicHeight(); final int left = child.getRight() + params.rightMargin; final int right = left + mDivider.getIntrinsicWidth(); mDivider.setBounds(left, top, right, bottom); mDivider.draw(canvas); } /** * 绘制水平间隔线 * * @param canvas * @param parent * 父布局,RecyclerView * @param position * item在父布局中所在的位置 */ private void drawHorizontalDivider(Canvas canvas, RecyclerView parent, int position) { final View child = parent.getChildAt(position); final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child .getLayoutParams(); final int top = child.getBottom() + params.bottomMargin; final int bottom = top + mDivider.getIntrinsicHeight(); final int left = child.getLeft() - params.leftMargin; final int right = child.getRight() + params.rightMargin + mDivider.getIntrinsicWidth(); mDivider.setBounds(left, top, right, bottom); mDivider.draw(canvas); } }