在开发中使用RecyclerVIew已经是很常见的了,它作为ListView 和GirdView 的加强版的存在,并且具有瀑布流的效果,so,堪称完美,
今天我要做的是使用RecyclerView展示相对应的网格条目 进行多选 多项操作 在长按的时候
1、 选择该条目
2、仿IOS卸载应用时抖动效果
好了先看下效果图
(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中的选中状态中设置