在开发中使用RecyclerVIew已经是很常见的了,它作为ListView 和GirdView 的加强版的存在,并且具有瀑布流的效果,so,堪称完美,

今天我要做的是使用RecyclerView展示相对应的网格条目 进行多选 多项操作 在长按的时候

1、 选择该条目

2、仿IOS卸载应用时抖动效果


好了先看下效果图

android 长按事件confirm 安卓开发长按事件_初始化

(CSDN 对图片的大小要求严格 所以这个gif 做的不太好 ,但是能表达想要表达的效果了)


好了 下面就来看一下具体实现

一、多选的RecyclerView 不重写可以实现吗?

答案是肯定的 ,当然能实现,以为我们可以对它的条目做我们想做的处理

1、1 RecyclerView的使用方法
1.1.1 搭建布局和初始化
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/grid_coordinatorLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ffffff"
    android:fitsSystemWindows="true">
    <android.support.v7.widget.RecyclerView
        android:id="@+id/grid_recycler"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>

这个和平常使用的没什么却别

初始化 ,RecyclerView 的初始化呢,需要注意的是给他设置相应的布局管理器,我的需求是Gird 所以我使用的GridLayoutManager

recyclerview = findViewById<View>(R.id.grid_recycler) as RecyclerView
        mLayoutManager = GridLayoutManager(this@GridActivity, 5, GridLayoutManager.VERTICAL, false)
        recyclerview!!.layoutManager = mLayoutManager

哦 , 需要说明的是,我是使用kotlin 写的

接着就是初始化数据,但是使用RecyclerView 这种多条目的组件的时候一般是需要刷新的,所以应该在初始化布局的时候直接初始化适配器,在拿到的数据的时候刷新适配器就OK了
初始化适配器

mList = ArrayList()
        mAdapter = GridAdapter(this, mList!!, mHandler)
        recyclerview!!.adapter = mAdapter

初始化适配器和设置就完成了 现在就那数据就行了,但是这个地方有个注意的地方,就是初始化的时候一定要记得在初始化Adapter之前初始化List 不然会出现错误的

OK ,现在就拿数据,我这边使用的是retrofit 获取数据的

1.1.2 获取数据

retrofit 的使用方法就不做解释了
具体步骤是

1. 使用注解定义抽象方法

@GET("json")
    fun getData(@Query("json") json: String): Call<List<TestData>>

2. TestData 是我测试数据的bean类

class TestData {
    /**
     * "id":0,
    "version":3.5,
    "name":"《人生》",
    "url":"http://localhost:9999/liqin.jpg"
     */
    var id: Int = -1
    var version: String? = null
    var name: String? = null
    var url: String? = null

}

使用kotlin 的时候有一个好处就是不用写set/get方法

3. 下载数据

在下载的过程中添加了OkHttpClient ,但是并不是使用OkHttpClient完成下载的,它只是在retrofit中打印一下拼接的url 和请求相关的信息

fun doGet(responsdBobyI: ResponsdBobyI) {
            val loggingInterceptor = HttpLoggingInterceptor(HttpLoggingInterceptor.Logger { message ->
                //打印retrofit日志
                Log.i("RetrofitLog", "retrofitBack = " + message)
            })
            loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
            var client = OkHttpClient.Builder().addInterceptor(loggingInterceptor).build()
            var retrofit = Retrofit.Builder()
                    .client(client)
                    .baseUrl("http://192.168.43.106:9999/")
                    //使用工厂模式创建gson 的解析器
                  .addConverterFactory(GsonConverterFactory.create())
                    .build()
            //生成API接口的实例对象
            var service = retrofit.create(HttpTestService::class.java)
            //调用接口中定义的业务方法
            //获取接口实现类的方法 获取服务器上的数据解析存在List<TestData> 中
            var data = service.getData("json")
            data.enqueue(object : Callback<List<TestData>> {
                override fun onFailure(call: Call<List<TestData>>?, t: Throwable?) {
                    Log.i("data", "onFailure")
                }
                override fun onResponse(call: Call<List<TestData>>?, response: Response<List<TestData>>?) {
                    var body = response?.body()
                    (0 until body!!.size)
                            .map { body[it] }
                            .forEach {  }
                    responsdBobyI.responsd(body)
                }
            })
        }

使用回调设置下载好的数据 responsdBobyI.responsd(body)
回调在这里

interface ResponsdBobyI {
    fun responsd(result: List<TestData>)
}

4、在获取数据的Activity中拿到数据

HttpManager.doGet(object : ResponsdBobyI {
            override fun responsd(result: List<TestData>) {
                for (i in result.indices) {
                    Log.e("shuju", result[i].url)
                }
                mList!!.addAll(result)
                mHandler.sendMessage(Message.obtain())
            }
        })

mList!!.addAll(result) 将数据添加到要在Adapter中使用的集合中,给Handler发送消息

@SuppressLint("HandlerLeak")
    private val mHandler = object : Handler() {
        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
            Log.e("shuju", "jkljkjlkjlkjljljlk")
            mAdapter!!.notifyDataSetChanged()
        }
    }

刷新适配器

好了这是完成了初始化 现在就是我们将要实现两种效果的Adapter了

RecyclerView 常用的适配器

class GridAdapter(private val mContext: Context, private val datas: List<TestData>, private val mHandler: Handler) : RecyclerView.Adapter<RecyclerView.ViewHolder>(), View.OnClickListener, View.OnLongClickListener

在定义的时候 实现了点击事件和长按事件

自定义ViewHolder 查找将要使用的控件 取名为MyViewHolder

internal inner class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val iv: ImageButton  //显示的卡片布局
        val select: LinearLayout //给选中的items 添加选中框背景的布局
        val items: RelativeLayout//将要添加抖动效果的布局

        init {
            iv = view.findViewById(R.id.iv)
            select = view.findViewById(R.id.select)
            items = view.findViewById(R.id.items)
        }
    }

返回item的布局

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val view = LayoutInflater.from(mContext
        ).inflate(R.layout.recyclerview_item, parent,
                false)
        val holder = MyViewHolder(view)
        view.setOnClickListener(this) //设置点击事件
        view.setOnLongClickListener(this)
        return holder
    }
    /**
    *返回条目的数量
    **/
override fun getItemCount(): Int {
        return datas.size
    }

展示条目

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        Glide.with(mContext).load(datas[position].url).into((holder as MyViewHolder).iv)
        view = holder.items
        if (defaultItem != -1) {
            if (mPos.contains(position)) { //长按的条目在这个集合中存在的时候标记为选中 之后所有的选中与否都操作这个集合
                var loadAnimation = AnimationUtils.loadAnimation(mContext, R.anim.shake)
                holder.items.startAnimation(loadAnimation)
                holder.select.visibility = View.VISIBLE
            } else {
                holder.select.visibility = View.GONE
            }
        }
    }

设置点击事件

interface OnRecyclerViewItemClickListener {
        fun onItemClick(view: View)
        fun onItemLongClick(view: View)
    }
fun setOnItemClickListener(listener: OnRecyclerViewItemClickListener) {
        mOnItemClickListener = listener
    }

设置点击事件这一块比较简单 就不说了

多选

定义 private var defaultItem = -1 标记条目 ,当条目不是-1 的时候显示选中效果,并且在设置的时候将选择的条目的position保存起来

fun setSelect(select: Int) {
        defaultItem = select
        mPos.add(select)
        notifyDataSetChanged()
    }

并且刷新适配器

返回的时候取消选中的效果

fun backRecovery() {
        mPos.clear()
        view?.clearAnimation()
        showShakeAnim?.end()
        notifyDataSetChanged()
    }

在Activity中的调用

mAdapter!!.setOnItemClickListener(object : GridAdapter.OnRecyclerViewItemClickListener {
            override fun onItemClick(view: View) {
                val position = recyclerview!!.getChildAdapterPosition(view)

            }

            override fun onItemLongClick(view: View) {
                var position = recyclerview!!.getChildAdapterPosition(view)
                mAdapter!!.setSelect(position) //获取到长安的position 调用选中的方法
            }
        })

在这中间的 当然会有要外加的逻辑 。。。

OK 还有一个就是在选中的时候 相应的items会想iOS的抖动的效果
好了 这个有好几种方法

第一种

fun shakeItems(view: View, shakeCode: Float): ObjectAnimator {
            val pvhScaleX = PropertyValuesHolder.ofKeyframe(View.SCALE_X, Keyframe.ofFloat(0f, 1f),
                    Keyframe.ofFloat(.1f, .9f),
                    Keyframe.ofFloat(.2f, .9f),
                    Keyframe.ofFloat(.3f, 1.1f),
                    Keyframe.ofFloat(.4f, 1.1f),
                    Keyframe.ofFloat(.5f, 1.1f),
                    Keyframe.ofFloat(.6f, 1.1f),
                    Keyframe.ofFloat(.7f, 1.1f),
                    Keyframe.ofFloat(.8f, 1.1f),
                    Keyframe.ofFloat(.9f, 1.1f),
                    Keyframe.ofFloat(1f, 1f))

            val pvhScaleY = PropertyValuesHolder.ofKeyframe(View.SCALE_Y,
                    Keyframe.ofFloat(0f, 1f),
                    Keyframe.ofFloat(.1f, .9f),
                    Keyframe.ofFloat(.2f, .9f),
                    Keyframe.ofFloat(.3f, 1.1f),
                    Keyframe.ofFloat(.4f, 1.1f),
                    Keyframe.ofFloat(.5f, 1.1f),
                    Keyframe.ofFloat(.6f, 1.1f),
                    Keyframe.ofFloat(.7f, 1.1f),
                    Keyframe.ofFloat(.8f, 1.1f),
                    Keyframe.ofFloat(.9f, 1.1f),
                    Keyframe.ofFloat(1f, 1f)
            )

            val pvhRotate = PropertyValuesHolder.ofKeyframe(View.ROTATION,
                    Keyframe.ofFloat(0f, 0f),
                    Keyframe.ofFloat(.1f, -3f * shakeCode),
                    Keyframe.ofFloat(.2f, -3f * shakeCode),
                    Keyframe.ofFloat(.3f, 3f * shakeCode),
                    Keyframe.ofFloat(.4f, -3f * shakeCode),
                    Keyframe.ofFloat(.5f, 3f * shakeCode),
                    Keyframe.ofFloat(.6f, -3f * shakeCode),
                    Keyframe.ofFloat(.7f, 3f * shakeCode),
                    Keyframe.ofFloat(.8f, -3f * shakeCode),
                    Keyframe.ofFloat(.9f, 3f * shakeCode),
                    Keyframe.ofFloat(1f, 0f)
            )

            return ObjectAnimator.ofPropertyValuesHolder(view, pvhScaleX, pvhScaleY, pvhRotate).setDuration(1000)

        }

第二种

fun showShakeAnim(view: View): ObjectAnimator {
            val anim = ObjectAnimator.ofFloat(view, "rotation", -2F, 2F)
            anim.duration = 100
            anim.interpolator = AccelerateDecelerateInterpolator()
            anim.repeatCount = Integer.MAX_VALUE
            anim.repeatMode = ObjectAnimator.REVERSE
            anim.start()
            return anim
        }

第三种

<?xml version="1.0" encoding="UTF-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="80"
    android:fromDegrees="-2"
    android:pivotX="50%"
    android:pivotY="50%"
    android:repeatCount="infinite"
    android:repeatMode="reverse"
    android:toDegrees="2" />

以上三种抖动效果都是可以实现的 ,使用的位置我们在Adapter中的onBindViewHolder中的选中状态中设置