> 文档中心 > recyclerView选中播放动画突破布局边界

recyclerView选中播放动画突破布局边界

在这里插入图片描述

recyclerView选中播放动画突破布局边界

  • 1、横向均分处理。
  • 2、左右无限滑。
  • 3、如何确保刚好滑动item的中间
  • 4、更新ViewHolder
  • 5、动画突破布局边界
  • 6、adapter
  • 7、下面的小圆点
  • 8、dialog的一些设置

如上所示,横向均分显示3个item,默认中间项目显示放大和播放动画,左右无限滑动,但是实际上只有5项,下面的几个小圆点显示目前是在第几项,每过5秒自动滑到下一个item。
要求:中间项放大且方播放动画,那么它看起来则要占据比较大的空间来给它进行动画播放,如果是动态改变View的属性的话,会无比复杂,因为必须是均分才能刚好显示3个item。
而且修改还需要无闪烁,你修改后还得修改回来,很容易就显示错乱。所以最后解决并没有真实去修改View的属性。
这里主要介绍recyclerView的处理。

1、横向均分处理。

基本上就是获取屏幕的宽减去两边padding再除以3,至于多出来的2dp是为了当:
向左或者向右滑动一个item,这个item刚好看不见,但是由于回收的基准线没过,导致它,看不见,但是还没被回收,在这里我们需要它马上进入回收复用池。

 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyHolder { val  itemView = LayoutInflater.from(parent.context).inflate(R.layout.recommend_female_users_rv_item, parent,false) //两边是54减去56,给两边预留一点点间隙,因为rv的回收有一条基准线,过了基准线会被回收。 //setItemViewCacheSize(0),当CacheView的缓存设置为0的时候,如果刚好当前item滑出屏幕之外一格,却没过基准线,导致回收不了。 //这个时候由于滑出屏幕之外我们控制不了,它滑回来的时候还带着动画,所以要让它被回收掉。 itemView.layoutParams.width = (width - Utils.dip2px(context, 56f)) / 3 return MyHolder(itemView)    }

//做出改变,拿到第一个可见View
var position = layoutManager?.findFirstVisibleItemPosition()
貌似拿到第一个,然后再+1就OK了。
但是在某些手机上,非常无奈地发现,它的可见item居然是5个,我非常郁闷。
那么只能再拿最后一个可见item:
var lastPosition = layoutManager?.findLastVisibleItemPosition()
然后判断是3个还是5个,如果是5个可见的+2,否则+1处理。

2、左右无限滑。

在adapter里:
返回Integer的最大数,基本上就可以满足无限滑了
override fun getItemCount(): Int = Integer.MAX_VALUE
onBindViewHolder的时候:
var realPosition = position%data.size,拿到它在数据集里面的位置
rv设置完adapter之后:
layoutManager?.scrollToPositionWithOffset(Integer.MAX_VALUE / 2 + 1, 0)

3、如何确保刚好滑动item的中间

LinearSnapHelper().attachToRecyclerView(rv)
默认提供了工具类,以前不知道的时候自己去计算判断然后再帮忙二次滑动,有点淡淡的忧伤。

监听rv的滑动当挺下来的时候由于我们让它刚好滑动中间,所以会有二次滑动,在我的手机上第一次滑动停下来的时候再次触发滑动是没有时间间隔的,所以我延迟20毫秒去做事。

   rv?.addOnScrollListener(object : RecyclerView.OnScrollListener() {     override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {  super.onScrollStateChanged(recyclerView, newState)  when (newState) {      SCROLL_STATE_IDLE -> {   delayExecute()   isNeedUpdateView = true   isScroll = false      }      else -> {   if (isNeedUpdateView) {isNeedUpdateView = falseisScroll = true   }      }  }     } })
  fun delayExecute() { timer?.schedule(object : TimerTask() {     override fun run() {  if (isNeedUpdateView) {      runOnUiThread {   updateSecondItem()      }  }     } }, 20)    }

同时把上次的itemView的动画复原

    private fun restoreItemView() { try {     //滑出屏幕之外直接FC,所以try,滑出屏幕之外的禁止掉ViewCache缓存,     //让所有的缓存都在pool里面,     // 最终都会走onBindViewHolder重新绑定,在绑定的时候初始化即可。     val viewhodler = rv?.getChildViewHolder(layoutManager?.findViewByPosition(location)!!) as RecommendFemaleUsersAdapter.MyHolder     viewhodler.rootView.clearAnimation()     viewhodler.rippleLayout.stopRippleAnimation() } catch (e : Exception) {     e.printStackTrace() }    }

如果从中间往左或者右滑动一格,当前item还在屏幕中,所以需要我们找到这个Viewholder,并把它的动画停止,view还原。
如果是滑动了两格,到了屏幕之外,我们是无法再拿到这个Viewholder的,会FC,所以加错误捕捉。
同时设置rv的缓存:
rv?.setItemViewCacheSize(0)
让所有的回收ViewHolder都进入pool池,这样复用的时候都会经过onBindViewHolder,我们在这个方法里初始化的时候停止所有的动画即可。

4、更新ViewHolder

直接通过findViewByPosition找到View,再通过getChildViewHolder找到ViewHolder

 fun updateView(position: Int) { try {     val viewhodler = rv?.getChildViewHolder(layoutManager?.findViewByPosition(position)!!) as RecommendFemaleUsersAdapter.MyHolder     viewhodler.rootView.startAnimation(animator)     viewhodler.rippleLayout.startRippleAnimation()     val viewhodlerLeft = rv?.getChildViewHolder(layoutManager?.findViewByPosition(position-1)!!) as RecommendFemaleUsersAdapter.MyHolder     viewhodlerLeft.rootView.clearAnimation()     viewhodlerLeft.rippleLayout.stopRippleAnimation()     val viewhodlerRight = rv?.getChildViewHolder(layoutManager?.findViewByPosition(position+1)!!) as RecommendFemaleUsersAdapter.MyHolder     viewhodlerRight.rootView.clearAnimation()     viewhodlerRight.rippleLayout.stopRippleAnimation() } catch (e : Exception) {     e.printStackTrace() }    }

或者
mAdapter?.notifyItemChanged(position + 1, 2)
第一个是position,第二个是类型,自己定义。

    override fun onBindViewHolder(holder: MyHolder, position: Int, payloads: MutableList<Any>) { if (payloads.isEmpty()) { //啥类型都没有则走默认的     super.onBindViewHolder(holder, position, payloads) } else if (payloads.size > 0 && payloads[0] is Integer){ //是我们自己定义的类型则进来     when(payloads[0] as Int) {  2 -> {      val animator = AnimationUtils.loadAnimation(context, R.anim.scale_textview)      holder.rootView.startAnimation(animator)      holder.rippleLayout.startRippleAnimation()  }     } }    }

这个更新的方式好处是默认拿到了holder: MyHolder, position: Int

5、动画突破布局边界

android:clipChildren=“false”
无论是属性突破还是补间动画的放大都可以。
要知道我们屏幕上看到的是很多层幕布通过计算裁剪clip之后得到的。
而设置这个属性是允许裁剪的时候保留对应的画面。
如果你设置了这个属性没有得到自己想要的结果绝对是可以调试的,具体要看对应的业务,之后通过网上去找对应的资料,这里我就不详细说了。

6、adapter

class RecommendFemaleUsersAdapter(val context: Context, var data: ArrayList<RecommendUserInfoBean>, val width: Int) : RecyclerView.Adapter<RecommendFemaleUsersAdapter.MyHolder>()  {    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyHolder { val  itemView = LayoutInflater.from(parent.context).inflate(R.layout.recommend_female_users_rv_item, parent,false) //两边是54减去56,给两边预留一点点间隙,因为rv的回收有一条基准线,过了基准线会被回收。 //setItemViewCacheSize(0),当CacheView的缓存设置为0的时候,如果刚好当前item滑出屏幕之外一格,却没过基准线,导致回收不了。 //这个时候由于滑出屏幕之外我们控制不了,它滑回来的时候还带着动画,所以要让它被回收掉。 itemView.layoutParams.width = (width - Utils.dip2px(context, 56f)) / 3 return MyHolder(itemView)    }    override fun getItemCount(): Int = Integer.MAX_VALUE    override fun onBindViewHolder(holder: MyHolder, position: Int) { var realPosition = position%data.size holder.avatar.loadUrl(data[realPosition].avatar) holder.rootView.cameraDistance holder.rippleLayout.stopRippleAnimation()    }    inner class MyHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { var rootView = itemView var rippleLayout: RippleLayout = itemView.findViewById(R.id.cwv_wave) var avatar: ImageView = itemView.findViewById<ImageView>(R.id.iv_avatar) var realAvatar: ImageView = itemView.findViewById<ImageView>(R.id.real_avatar)    }    //暂时弃用当前更新方式    override fun onBindViewHolder(holder: MyHolder, position: Int, payloads: MutableList<Any>) { if (payloads.isEmpty()) {     super.onBindViewHolder(holder, position, payloads) } else if (payloads.size > 0 && payloads[0] is Integer){     when(payloads[0] as Int) {  2 -> {      val animator = AnimationUtils.loadAnimation(context, R.anim.scale_textview)      holder.rootView.startAnimation(animator)      holder.rippleLayout.startRippleAnimation()  }     } }    }

在onCreateViewHolder方法里,设置View的属性,对item是没有问题的,如果你原来设置了居中,那么还是会居中。
如果你是在onBindViewHolder方法里,设置View的属性。
那么问题来了,你会发现你的itemView里面的控件整体的位置属性设置失效了。
由于我这里不需要,暂时没有跟进解决方案。
是因为我开始不是按照补间动画的方案去处理,而是实际上改变View的属性,最后发现是个神坑,引出了N多问题,最后无奈回归这种处理方式。

7、下面的小圆点

添加小圆点

  fun addIndicationView() { for (index in 0 until mData.size) {     val textView = TextView(context)     val layoutParams =  LinearLayout.LayoutParams(Utils.dip2px(context, 5f), Utils.dip2px(context, 5f))     layoutParams.setMargins(10, 0, 0, 0)     textView.layoutParams = layoutParams     textView.setBackgroundResource(R.drawable.custom_indication)     textView.isEnabled = false     circleLayout?.addView(textView) }    }

更新小圆点

    fun updateIndicator(newPosition: Int) { //将旧的indicator复原 circleLayout?.getChildAt(currentPosition)?.isEnabled = false currentPosition = newPosition%mData.size //设置新的indicator circleLayout?.getChildAt(currentPosition)?.isEnabled = true    }

小圆点选择器效果

<?xml version="1.0" encoding="utf-8"?><selector xmlns:android="http://schemas.android.com/apk/res/android">    <item android:state_enabled="true"> <shape android:shape="oval">     <solid android:color="@color/indicator_check" />     <corners android:radius="15dp"/> </shape>    </item>    <item> <shape android:shape="oval">     <solid android:color="@color/indicator_uncheck" />     <corners android:radius="15dp"/> </shape>    </item></selector>

8、dialog的一些设置

style

<style name="NormalDialog" parent="android:style/Theme.Dialog"> <item name="android:windowBackground">@android:color/transparent</item> <item name="android:windowNoTitle">true</item> <item name="android:windowFrame">@null</item> <item name="android:windowIsFloating">true</item> <item name="android:backgroundDimEnabled">true</item>    </style>

设置全屏

 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.recommend_remale_users_dialog) window?.setGravity(Gravity.CENTER) window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) this.setCanceledOnTouchOutside(false) val dm = mContext.resources.displayMetrics width = dm.widthPixels initView() initData() initListener()    }

回调:private val clickAction: (Boolean) -> Unit
我这里定义的是Boolean,根据情况自己定义。
clickAction.invoke(isNotMoreShowDialog)
接收参数叫clickAction的函数,传参是Boolean,Unit:没有返回值。
相比java去定义接口等操作,kotlin用起来简直不要太爽。