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;
}
}