Android 仿QQ界面的 RecyclerView 侧滑删除效果

    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



猜你喜欢

转载自blog.csdn.net/weixin_41454168/article/details/79608594