IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> recyclerView选中播放动画突破布局边界 -> 正文阅读

[移动开发]recyclerView选中播放动画突破布局边界

在这里插入图片描述


如上所示,横向均分显示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 = false
                            isScroll = 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用起来简直不要太爽。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-01-12 00:07:14  更:2022-01-12 00:09:15 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 10:35:16-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码