RecyclerView 是 ListView 的进阶,实现方法和 ListView 类似,但是多了一些动画效果,其他方面也有优化,有兴趣的自己去了解!
思路:
1、添加支持包
2、在显示界面的 xml 中布局占位
3、自定义一个可以水平滚动的 View 布局控件(侧滑删除)
4、调用自定义的侧滑控件,实现单个 RecyclerView 的 xml 布局
5、写一个与自定义侧滑控件匹配的适配器
6、在 Activity 调用并插入数据,实现效果
效果展示:
侧滑出现一个或多个按钮,点击实现想要的效果
实现详情:
1、添加支持包
在 build.gradle 下的 dependencies 中添加:
compile 'com.android.support:recyclerview-v7:25.2.0'
2、在显示界面的 xml 中布局占位
这里是在 activity_main.xml 中
android.support.v7.widget.RecyclerView android:layout_weight="1" android:id="@+id/recycler" android:layout_width="match_parent" android:layout_height="0dp" android:orientation="vertical" android:overScrollMode="never"/>
这只是主要的布局,其他的布局可以根据自己需要自己定义
3、自定义一个可以水平滚动的 View 布局控件(侧滑删除)
这里,自定义 RecyclerItemView 类,继承 HorizontalScrollView 具体如下:
import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.widget.HorizontalScrollView; import android.widget.LinearLayout; /** * Created by Administrator on 2018/3/16 0016. * 具有滑动效果的 Item 需要继承水平滚动视图 */ public class RecyclerItemView extends HorizontalScrollView{ private LinearLayout slide;//滑动弹出的按钮容器 private int slideWidth; // 滑动弹出这个控件的宽度 private onSlidingButtonListener onSbl;//滑动按钮侦听器 private Boolean isOpen = false;//判断是否有删除按钮被打开 public RecyclerItemView(Context context) { this(context, null); } public RecyclerItemView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public RecyclerItemView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.setOverScrollMode(OVER_SCROLL_NEVER); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); slide = (LinearLayout) findViewById(R.id.slide); } //通过布局获取按钮宽度 @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); if(changed){ this.scrollTo(0,0); //获取水平滚动条可以滑动的范围,即右侧按钮的宽度 slideWidth = slide.getWidth(); } } //触摸事件 @Override public boolean onTouchEvent(MotionEvent ev) { int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: onSbl.onDownOrMove(this); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: changeScrollx(); return true; default: break; } return super.onTouchEvent(ev); } //滚动改变(拖动多少显示多少,根据手势变化) @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); slide.setTranslationX(l - slideWidth); } // 按滚动条被拖动距离判断关闭或打开菜单 (被拖动的距离有没有隐藏或显示控件的一半以上?) public void changeScrollx(){ //判断拖动的距离有没有超过删除按钮的一半 if(getScrollX() >= (slideWidth/2)){ //推动了一半以上就打开 this.smoothScrollTo(slideWidth, 0); isOpen = true; onSbl.onMenuIsOpen(this); }else{ //没有一半以上就关上 this.smoothScrollTo(0, 0); isOpen = false; } } // 关闭菜单 public void closeMenu() { if (!isOpen){ return; } this.smoothScrollTo(0, 0); isOpen = false; } //设置滑动按钮监听器 public void setSlidingButtonListener(onSlidingButtonListener listener){ onSbl = listener; } //滑动按钮侦听器 public interface onSlidingButtonListener{ void onMenuIsOpen(View view); void onDownOrMove(RecyclerItemView recycler); } }
4、调用自定义的侧滑控件,实现单个 RecyclerView 的 xml 布局
和 ListView 一样,需要根据需求定义一个类似于模板的 xml 布局
这里则调用 第三步 定义的布局,实现想要的效果 item_recycler.xml :
<?xml version="1.0" encoding="utf-8"?> <com.win.recyclerqq.RecyclerItemView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="60dp" android:background="@android:color/white" android:layout_marginBottom="1dp"> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:id="@+id/slide" android:layout_toRightOf="@+id/layout_left" android:layout_width="wrap_content" android:layout_height="match_parent" android:orientation="horizontal"> <TextView android:id="@+id/other" android:layout_height="match_parent" android:layout_width="80dp" android:gravity="center" android:background="@drawable/button_yellow" android:text="其 他" /> <TextView android:id="@+id/delete" android:layout_height="match_parent" android:layout_width="80dp" android:gravity="center" android:background="@drawable/button_red" android:text="删 除"/> </LinearLayout> <LinearLayout android:id="@+id/layout_left" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/content_white" android:orientation="horizontal"> <ImageView android:id="@+id/image" android:layout_width="80dp" android:layout_height="50dp" android:layout_gravity="center" android:src="@mipmap/ic_launcher"/> <LinearLayout android:layout_weight="1" android:layout_width="0dp" android:layout_height="wrap_content" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingTop="5dp" android:paddingBottom="2dp" android:orientation="horizontal"> <TextView android:id="@+id/title" android:layout_weight="1" android:layout_width="0dp" android:layout_height="wrap_content" android:textSize="18sp" android:textColor="#000000" android:text="android开发交流群" /> <TextView android:id="@+id/time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingRight="10dp" android:paddingTop="10dp" android:textColor="#747373" android:textSize="12sp" android:text="11:44"/> </LinearLayout> <TextView android:id="@+id/content" android:layout_width="match_parent" android:layout_height="wrap_content" android:textColor="#747373" android:textSize="14sp" android:text="广州-悠悠哉:RecyclerView很容易" /> </LinearLayout> </LinearLayout> </RelativeLayout> </com.win.recyclerqq.RecyclerItemView>
可以看到 其他 和 删除 两个控件的位置是相对于下面的一个模板布局的,而下面的模板布局又横向占满了
这就可以知道,在初始显示时,其他 和 删除 两个控件是被隐藏在了右边
这个布局中有一些背景布局是自定义的布局 (
@drawable/button_yellow
@drawable/button_red
@drawable/content_white
),详情请见:
附录一
5、写一个与自定义侧滑控件匹配的适配器
定义一个 RecyclerViewAdapter 适配器:
import android.content.Context; import android.graphics.Bitmap; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import java.util.List; /** * Created by Administrator on 2018/3/16 0016. * item_recycler.xml 的适配器 */ public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.SimpleHolder> implements RecyclerItemView.onSlidingButtonListener{ private Context context; private List<Bitmap> dataImage; //头像(谁的头像) private List<String> dataTitle; //标题(谁的消息) private List<String> datasContent; //内容(消息内容) private List<String> datasTime; //时间(消息时间) private onSlidingViewClickListener onSvcl; private RecyclerItemView recyclers; public RecyclerViewAdapter(Context context) { this.context = context; } public RecyclerViewAdapter(Context context, List<Bitmap> dataImage, List<String> dataTitle, List<String> datasContent, List<String> datasTime) { this.context = context; this.dataImage = dataImage; this.dataTitle = dataTitle; this.datasContent = datasContent; this.datasTime = datasTime; } @Override public SimpleHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(context).inflate(R.layout.item_recycler, parent, false); return new SimpleHolder(view); } @Override public void onBindViewHolder(final SimpleHolder holder, final int position) { holder.image.setImageBitmap(dataImage.get(position)); holder.title.setText(dataTitle.get(position)); holder.content.setText(datasContent.get(position)); holder.time.setText(datasTime.get(position)); holder.layout_left.getLayoutParams().width = RecyclerUtils.getScreenWidth(context); holder.layout_left.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText(context,"做出操作,进入新的界面或弹框",Toast.LENGTH_SHORT).show(); //判断是否有删除菜单打开 if (menuIsOpen()) { closeMenu();//关闭菜单 } else { //获得布局下标(点的哪一个) int subscript = holder.getLayoutPosition(); onSvcl.onItemClick(view, subscript); } } }); holder.other.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText(context,"其他:"+position,Toast.LENGTH_SHORT).show(); } }); holder.delete.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText(context,"删除了:"+position,Toast.LENGTH_SHORT).show(); int subscript = holder.getLayoutPosition(); onSvcl.onDeleteBtnCilck(view,subscript); } }); } @Override public int getItemCount() { return dataImage.size(); } @Override public void onMenuIsOpen(View view) { recyclers = (RecyclerItemView) view; } @Override public void onDownOrMove(RecyclerItemView recycler) { if(menuIsOpen()){ if(recyclers != recycler){ closeMenu(); } } } class SimpleHolder extends RecyclerView.ViewHolder { public ImageView image; public TextView title; public TextView content; public TextView time; public TextView other; public TextView delete; public LinearLayout layout_left; public SimpleHolder(View view) { super(view); image = (ImageView) view.findViewById(R.id.image); title = (TextView) view.findViewById(R.id.title); content = (TextView) view.findViewById(R.id.content); time = (TextView) view.findViewById(R.id.time); other = (TextView) view.findViewById(R.id.other); delete = (TextView) view.findViewById(R.id.delete); layout_left = (LinearLayout) view.findViewById(R.id.layout_left); ((RecyclerItemView)view).setSlidingButtonListener(RecyclerViewAdapter.this); } } //删除数据 public void removeData(int position){ dataImage.remove(position); // notifyDataSetChanged(); notifyItemRemoved(position); } //关闭菜单 public void closeMenu() { recyclers.closeMenu(); recyclers = null; } // 判断是否有菜单打开 public Boolean menuIsOpen() { if(recyclers != null){ return true; } return false; } //设置在滑动侦听器上 public void setOnSlidListener(onSlidingViewClickListener listener) { onSvcl = listener; } // 在滑动视图上单击侦听器 public interface onSlidingViewClickListener { void onItemClick(View view, int position); void onDeleteBtnCilck(View view, int position); } }
在这里实现了各个控件的控制效果,其中:
RecyclerUtils.getScreenWidth(context)是计算控件的宽度的,详情见: 附录二
6、在 Activity 调用并插入数据,实现效果
最后需要在与 activity_main.xml 相关联的 Activity 中 调用适配器,并传入数据
import android.graphics.Bitmap; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.View; import android.widget.Toast; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity implements RecyclerViewAdapter.onSlidingViewClickListener{ private RecyclerView recycler; //在xml 中 RecyclerView 布局 private RecyclerViewAdapter rvAdapter; //item_recycler 布局的 适配器 //设置数据 private List<Bitmap> dataImage; //头像(谁的头像) private List<String> dataTitle; //标题(谁的消息) private List<String> datasContent; //内容(消息内容) private List<String> datasTime; //时间(消息时间) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //初始化RecyclerView init(); //将 RecyclerView 布局设置为线性布局 recycler.setLayoutManager(new LinearLayoutManager(this)); datas();//插入数据 //更新界面 updateInterface(); } public void init(){ recycler = (RecyclerView)findViewById(R.id.recycler); } public void updateInterface(){ if (rvAdapter == null) { //实例化 RecyclerViewAdapter 并设置数据 rvAdapter = new RecyclerViewAdapter(this, dataImage, dataTitle, datasContent, datasTime); //将适配的内容放入 mRecyclerView recycler.setAdapter(rvAdapter); //控制Item增删的动画,需要通过ItemAnimator DefaultItemAnimator -- 实现自定义动画 recycler.setItemAnimator(new DefaultItemAnimator()); }else { //强调通过 getView() 刷新每个Item的内容 rvAdapter.notifyDataSetChanged(); } //设置滑动监听器 (侧滑) rvAdapter.setOnSlidListener(this); } //通过 position 区分点击了哪个 item @Override public void onItemClick(View view, int position) { //在这里可以做出一些反应(跳转界面、弹出弹框之类) Toast.makeText(MainActivity.this,"点击了:" + position,Toast.LENGTH_SHORT).show(); } //点击删除按钮时,根据传入的 position 调用 RecyclerAdapter 中的 removeData() 方法 @Override public void onDeleteBtnCilck(View view, int position) { rvAdapter.removeData(position); } public void datas(){ dataImage = new ArrayList<Bitmap>(); //头像(谁的头像) dataTitle = new ArrayList<String>(); //标题(谁的消息) datasContent = new ArrayList<String>(); //内容(消息内容) datasTime = new ArrayList<String>(); //时间(消息时间) dataImage.add(RecyclerUtils.toRoundBitmap(RecyclerUtils.bitmaps(R.mipmap.a1, this))); dataImage.add(RecyclerUtils.toRoundBitmap(RecyclerUtils.bitmaps(R.mipmap.a2, this))); dataImage.add(RecyclerUtils.toRoundBitmap(RecyclerUtils.bitmaps(R.mipmap.a3, this))); dataImage.add(RecyclerUtils.toRoundBitmap(RecyclerUtils.bitmaps(R.mipmap.a4, this))); dataImage.add(RecyclerUtils.toRoundBitmap(RecyclerUtils.bitmaps(R.mipmap.a5, this))); dataImage.add(RecyclerUtils.toRoundBitmap(RecyclerUtils.bitmaps(R.mipmap.a6, this))); dataImage.add(RecyclerUtils.toRoundBitmap(RecyclerUtils.bitmaps(R.mipmap.a7, this))); dataImage.add(RecyclerUtils.toRoundBitmap(RecyclerUtils.bitmaps(R.mipmap.a8, this))); dataImage.add(RecyclerUtils.toRoundBitmap(RecyclerUtils.bitmaps(R.mipmap.a9, this))); dataImage.add(RecyclerUtils.toRoundBitmap(RecyclerUtils.bitmaps(R.mipmap.a10, this))); dataImage.add(RecyclerUtils.toRoundBitmap(RecyclerUtils.bitmaps(R.mipmap.a11, this))); dataImage.add(RecyclerUtils.toRoundBitmap(RecyclerUtils.bitmaps(R.mipmap.a12, this))); dataTitle.add("Android开发交流群"); dataTitle.add("R语言初级入门学习"); dataTitle.add("刘亦菲"); dataTitle.add("策划书交流群"); dataTitle.add("15生态宜居学院学生群"); dataTitle.add("湘环资助 (助学贷款)"); dataTitle.add("湘环编程研讨会"); dataTitle.add("丰风"); dataTitle.add("阿娇"); dataTitle.add("图书馆流通服务交流群"); dataTitle.add("one3胡了"); dataTitle.add("读者协会策划部"); datasContent.add("广州_Even:[图片]"); datasContent.add("轻舟飘飘:auto基本不准"); datasContent.add("不会的"); datasContent.add("残留的余温。:分享[熊猫直播]"); datasContent.add("刘老师:[文件]2018年6月全国大学……"); datasContent.add("17级园林"); datasContent.add("黄晓明:20k不到"); datasContent.add("[文件]编程之美"); datasContent.add("i5的处理器,比较稳定,蛮好的"); datasContent.add("寥寥:好的,谢谢老师"); datasContent.add("易天:阿龙还在面试呢"); datasContent.add("策划部陈若依、:请大家把备注改好"); datasTime.add("16:24"); datasTime.add("14:37"); datasTime.add("10:58"); datasTime.add("昨天"); datasTime.add("昨天"); datasTime.add("昨天"); datasTime.add("星期三"); datasTime.add("星期三"); datasTime.add("星期二"); datasTime.add("星期二"); datasTime.add("星期二"); datasTime.add("星期一"); } }
在 第五步 写适配器时,就写了可以传入参数的构造方法,这时,我们只要调用这个构造方法将参数传入就好了
现在这里传入的是固定的模拟参数、在实际应用中可以根据服务去传来的参数动态的赋予。
下面这句代码是将 res 中的图片转换成 Bitmap 并裁切成圆,实现代码见:附录二
RecyclerUtils.toRoundBitmap(RecyclerUtils.bitmaps(R.mipmap.a1, this))
附录一
三个背景布局格式都如下:
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_pressed="true"> <layer-list > <item > <shape > <solid android:color="#FF0000"/> </shape> </item> <item > <shape > <solid android:color="#22000000"/> </shape> </item> </layer-list> </item> <item > <shape > <solid android:color="#FF0000"/> </shape> </item> </selector>
只需要根据需求更改 color 的值就行了
附录二
获取控件宽度、转换图片格式、裁切图片这些都是在自定义的一个 Utils 类中实现的:
import android.content.Context; import android.content.res.Resources; import android.graphics.BitmapFactory; import android.util.DisplayMetrics; import android.view.WindowManager; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.RectF; /** * Created by Administrator on 2018/3/16 0016. * 包括获取控件宽度、切割位图(圆形)、将res里的图片转成位图 */ public class RecyclerUtils { //获取屏幕宽度 public static int getScreenWidth(Context context) { //窗口管理器 WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE ); //显示度量 DisplayMetrics displayMetrics = new DisplayMetrics(); windowManager.getDefaultDisplay().getMetrics(displayMetrics); return displayMetrics.widthPixels; //返回屏幕宽度像素 } //这个方法将位图切割成圆形 public static Bitmap toRoundBitmap(Bitmap bitmap) { int width = bitmap.getWidth(); int height = bitmap.getHeight(); float roundPx; float left, top, right, bottom, dst_left, dst_top, dst_right, dst_bottom; if (width <= height) { roundPx = width / 2; left = 0; top = 0; right = width; bottom = width; height = width; dst_left = 0; dst_top = 0; dst_right = width; dst_bottom = width; } else { roundPx = height / 2; float clip = (width - height) / 2; left = clip; right = width - clip; top = 0; bottom = height; width = height; dst_left = 0; dst_top = 0; dst_right = height; dst_bottom = height; } Bitmap output = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(output); final int color = 0xff424242; final Paint paint = new Paint(); final Rect src = new Rect((int) left, (int) top, (int) right, (int) bottom); final Rect dst = new Rect((int) dst_left, (int) dst_top, (int) dst_right, (int) dst_bottom); final RectF rectF = new RectF(dst); paint.setAntiAlias(true); canvas.drawARGB(0, 0, 0, 0); paint.setColor(color); canvas.drawCircle(roundPx, roundPx, roundPx, paint); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); canvas.drawBitmap(bitmap, src, dst, paint); return output; } //将res里的图片转换成位图 (裁剪需要) public static Bitmap bitmaps(int image,Context context){ Resources res = context.getResources(); Bitmap bitmap = BitmapFactory.decodeResource(res, image); return bitmap; } }
源码:https://github.com/iscopy/RecyclerQQ