仿淘宝头条滚动广告的实现。
1、需求示意
- 淘宝效果
近期项目中需要实现上图中类似于淘宝头条的滚动效果,查阅相关资料之后发现Android原生的ViewFlipper 和 AdapterViewFlipper就可以做到上述效果,所以,做一波相应的总结吧。下图为仿写效果。
- 实现的效果
2、ViewFlipper和AdapterViewFlipper介绍
(1)、ViewFlipper
1) 继承关系
如上图,我们可以获知 ViewFlipper是ViewAnimator 的子类。而ViewAnimator是一个容器,当其中包含多个子View时,可以设置子View之间切换/显示时的动画效果。同一时刻只有一个子View显示在界面中
2) 常用属性
- autoStart
是否自动切换子View。true——自动切换;false——不自动切换,此时,需要手动调用 startFlipping()/stopFlipping()来启动或停止切换。该属性对应的方法为:setAutoStart(boolean)
- flipInterval
子View之间切换的间隔时长,单位毫秒 ms
- inAnimation
定义view显示时的动画效果,该属性继承自 ViewAnimator
- outAnimation
定义view隐藏时显示的动画效果,该属性继承自 ViewAnimator
- animateFirstView
第一个子View是否显示动画效果.(注意,这个设置之后并没有看出啥效果)
(2)、AdapterViewFlipper
1)、继承关系
如上图,AdapterViewFlipper继承自AdapterViewAnimator,而后者又继承自AdapterView,看到这里是不是很熟悉?对的,ListView也是AdapterView的子类——确切的说是孙子类。
2)、常用属性
参考 ViewFlipper 中的常用属性。
(3)、两者的对比
1)、复用性
ViewFlipper中的子view只能在xml中提前写好,或者在代码中通过 addView 的形式添加,这种形式不具有复用性质,数据量比较少的时候可以使用
AdapterViewFlipper 具有复用属性,可以处理大量的数据
2)、动画效果
- ViewFlipper在设置动画时接收的是Animation——补间动画,可以是单一动画效果,也可以是动画集合。
- AdapterViewFlipper 在设置动画的时候接收的是 ObjectAnimator,只能是单一动画,不能使用动画集合
3)、关于默认的interval
- ViewFlipper的默认Interval为3000ms,即 3秒
- AdapterViewFlipper的默认Interval为10000ms,即 10秒
基于以上对比,在实际使用的时候可以根据需求动态决定使用哪一个。
3、完整示例代码——kotlin版:
关于ViewFlipper和AdapterViewFlipper的其他注意事项,请查阅代码中的注释内容
- ViewFlipperActivity.kt
import android.animation.AnimatorInflater
import android.animation.ObjectAnimator
import android.graphics.Color
import android.os.Bundle
import android.view.animation.TranslateAnimation
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.cnpeng.android2.R
import kotlinx.android.synthetic.main.activity_view_flipper.*
/**
* CnPeng 2018/11/30 12:14 PM
* 功用:仿淘宝客户端中的 淘宝头条滚动广告
* 说明:
* - 1、
* - 2、除了这两种方式之外,还可以考虑使用RecyclerView 或 ViewPager实现 该效果,代码省略
*/
class ViewFlipperActivity : AppCompatActivity() {
private lateinit var mActivity: ViewFlipperActivity
var mStrList = mutableListOf()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_view_flipper)
initCommonVariables()
initViewFlipper()
initAdapterViewFlipper()
}
private fun initCommonVariables() {
mActivity = this
for (i in 1..15) {
mStrList.add("这是第" + i + "个元素")
}
}
private fun initViewFlipper() {
//CnPeng 2018/11/30 2:21 PM 添加view数据
for ((index, str) in mStrList.withIndex()) {
val textView = TextView(mActivity)
textView.text = str
if (0 == index % 2) {
textView.setTextColor(Color.parseColor("#FFFF0000"))
//CnPeng 2018/11/30 1:38 PM 这种方式也可以
//textView.setTextColor(Color.RED)
} else {
textView.setTextColor(ContextCompat.getColor(mActivity, R.color.c_666666))
}
viewFlipper.addView(textView)
}
//setViewFlipperAnimation()
}
/**
* CnPeng 2018/11/30 2:22 PM
* 功用:设置ViewFlipper的切换动画
* 说明:这是代码设置的方式,也可以从布局文件中通过 inanimation 、outanimation 属性设置
* -1、在ViewFlipper中 DEFAULT_INTERVAL 为 3000,即 3秒。
* -2、在执行inAnimation 和 outAnimation 时,如果两者的duration之和等于我们设置的 interval,则动画能正常展示;否则,动画不能正常展示
* -3、可以使用动画集合,因为在设置动画时接收的对象为 Animation,而 AnimationSet 是 Animation的子类
*/
private fun setViewFlipperAnimation() {
val inAnimation = TranslateAnimation(0f, 0f, 100f, 0f)
inAnimation.duration = 1500
//CnPeng 2018/11/30 4:20 PM 此处不需要调用start。viewFlipper在展示child的时候会主动触发动画。但是,调用了在界面上也看不出特殊
// inAnimation.start()
val outAnimation = TranslateAnimation(0f, 0f, 0f, -100f)
outAnimation.duration = 1500
// outAnimation.start()
viewFlipper.inAnimation = inAnimation
viewFlipper.outAnimation = outAnimation
}
private fun initAdapterViewFlipper() {
val flipperAdapter = AdapterFlipperViewAdapter(mStrList, mActivity)
adapterViewFlipper.adapter = flipperAdapter
// setAdapterViewFlipperAnimator()
// setAdapterViewFlipperAnimator2()
}
/**
* CnPeng 2018/11/30 4:31 PM
* 功用:
* 说明:
* -1、在AdapterViewFlipper中 DEFAULT_INTERVAL 为 10000,即 10秒。
* -2、在执行inAnimation 和 outAnimation 时,如果两者的duration之和 小于等于 我们设置的 interval,则动画能正常展示;否则,动画不能正常展示
* -3、不能使用动画集合,因为设置动画时只接收 ObjectAnimator及其子类,而 AnimatorSet 是其叔叔
* -4、如果是从xml中设置动画的话,初始化view的时候,第一条也会执行一个渐入的动画;如果是通过代码设置,则第一条直接显示在视图中
*/
private fun setAdapterViewFlipperAnimator() {
//CnPeng 2018/11/30 4:14 PM 注意,下面这一行是从Java代码转义过来的,在Java环境下,最后一个参数我们会手动构造一个float[] ,但在kotlin中会报错
// val animator = ObjectAnimator.ofFloat(adapterViewFlipper, "translationY", arrayOf(100f, 0f))
//CnPeng 2018/11/30 4:15 PM 最后一个参数是可变数组,我们不需要构造array,直接写array的值即可。
val inAnimator = ObjectAnimator.ofFloat(adapterViewFlipper, "translationY", 100f, 0f)
inAnimator.duration = 1500
//CnPeng 2018/11/30 4:20 PM 此处不需要调用start。viewFlipper在展示child的时候会主动触发动画。如果调用了,执行动画的将是flipper本身
// inAnimator.start()
val outAnimator = ObjectAnimator.ofFloat(adapterViewFlipper, "translationY", 0f, -100f)
outAnimator.duration = 1500
// outAnimator.start()
adapterViewFlipper.inAnimation = inAnimator
adapterViewFlipper.outAnimation = outAnimator
}
/**
* CnPeng 2018/11/30 4:31 PM
* 功用:
* 说明:
* -1、在AdapterViewFlipper中 DEFAULT_INTERVAL 为 10000,即 10秒。
* -2、在执行inAnimation 和 outAnimation 时,如果两者的duration之和 小于等于 我们设置的 interval,则动画能正常展示;否则,动画不能正常展示
* -3、不能使用动画集合,因为设置动画时只接收 ObjectAnimator及其子类,而 AnimatorSet 是其叔叔
* -4、如果是从xml中设置动画的话,初始化view的时候,第一条也会执行一个渐入的动画;如果是通过代码设置,则第一条直接显示在视图中
*/
private fun setAdapterViewFlipperAnimator2() {
val inAnimator = AnimatorInflater.loadAnimator(mActivity, R.animator.inanimator_flipper)
val outAnimator = AnimatorInflater.loadAnimator(mActivity, R.animator.outanimator_flipper)
adapterViewFlipper.inAnimation = inAnimator as ObjectAnimator?
adapterViewFlipper.outAnimation = outAnimator as ObjectAnimator?
}
}
- activity_view_flipper.xml
- AdapterViewFlipperAdapter.kt
/**
* 作者:CnPeng
* 时间:2018/11/30
* 功用:AdapterFlipperView使用的适配器
* 其他:
*/
class AdapterFlipperViewAdapter (strList: MutableList, mActivity: ViewFlipperActivity) : BaseAdapter() {
var mStrList = strList
var mContext = mActivity
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
val itemHolder: ItemHolder
//CnPeng 2018/11/30 3:50 PM 这里略微有点怪,不能直接给 convertView复制,一直提示convertView是常量
var itemView = convertView
if (null == itemView) {
itemView = TextView(mContext)
itemHolder = ItemHolder()
itemHolder.textView = itemView;
} else {
itemHolder = itemView.tag as ItemHolder
}
itemHolder.textView.text = mStrList[position]
if (0 == position % 2) {
itemHolder.textView.setTextColor(Color.BLACK)
} else {
itemHolder.textView.setTextColor(Color.parseColor("#ff0000"))
}
return itemView
}
override fun getItem(position: Int): Any {
return mStrList[position]
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getCount(): Int {
return mStrList.size
}
class ItemHolder {
lateinit var textView: TextView
}
}
- res/anim/inanimation_viewflipper.xml
- res/anim/outanimation_viewflipper.xml
- res/animator/inanimator_flipper.xml
- res/animator/outanimator_flipper.xml
4、附录
- 项目地址
https://github.com/CnPeng/CnPengAndroid2。
文中内容在上述目录中的 b03_view_flipper 包下