遇到这样的需求:
顶部的按钮之间切换,以实心形状从一个后面滑动到另一个后面。
第一反应是联想到了TabLayout底部的指示器滑动,建议去看看它的源码实现。
这里我就不扯它源码了,习惯直接上...差点忘了,先上实现效果:
#1 xml布局 >>> view_sliding_tab_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="60dp"
android:orientation="horizontal"
android:padding="5dp">
<View
android:id="@+id/viewIndicator"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:background="@drawable/bg_button_category_purple"
android:elevation="5dp"
android:visibility="visible" />
<TextView
android:id="@+id/left_text"
android:layout_width="100dp"
android:layout_height="match_parent"
android:elevation="6dp"
android:gravity="center"
android:textColor="@color/colorWhite"
android:text="CATE A" />
<TextView
android:id="@+id/mid_text"
android:layout_width="70dp"
android:layout_height="match_parent"
android:layout_marginStart="5dp"
android:elevation="6dp"
android:textColor="@color/colorBlack"
android:layout_toEndOf="@+id/left_text"
android:gravity="center"
android:text="CATE B" />
<TextView
android:id="@+id/right_text"
android:layout_width="100dp"
android:layout_height="match_parent"
android:layout_toEndOf="@+id/mid_text"
android:layout_marginStart="5dp"
android:textColor="@color/colorBlack"
android:elevation="6dp"
android:gravity="center"
android:text="CATE C" />
</RelativeLayout>
cc: viewIndicator是用来根据按钮切换来调整移动位置显示
#2 CustomView >>> SlidingTabLayout
class SlidingTabLayout(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
private var subInventoryText: TextView
private var selectedText: TextView
private var indicatorLeft = -1
private var indicatorRight = -1
private var indicatorAnimator: ValueAnimator? = null
private var isIndicatorAnimatorEnd: Boolean = true
companion object {
private const val DEFAULT_TOGGLE_DURATION: Long = 300
}
init {
LayoutInflater.from(context).inflate(R.layout.view_sliding_tab_layout2, this, true)
//init text
selectedText = left_text
subInventoryText = left_text
selectedText.setTextColor(resources.getColor(R.color.colorWhite))
left_text.setOnClickListener {
if (isIndicatorAnimatorEnd && subInventoryText != left_text) {
isIndicatorAnimatorEnd = false
subInventoryText = left_text
animateIndicatorToPosition()
toggleTabView()
}
}
mid_text.setOnClickListener {
if (isIndicatorAnimatorEnd && subInventoryText != mid_text) {
isIndicatorAnimatorEnd = false
subInventoryText = mid_text
animateIndicatorToPosition()
toggleTabView()
}
}
right_text.setOnClickListener {
if (isIndicatorAnimatorEnd && subInventoryText != right_text) {
isIndicatorAnimatorEnd = false
subInventoryText = right_text
animateIndicatorToPosition()
toggleTabView()
}
}
}
private fun toggleTabView() {
subInventoryText.setTextColor(resources.getColor(R.color.colorWhite))
selectedText.setTextColor(resources.getColor(R.color.colorBlack))
}
private fun setIndicatorPosition(left: Int, right: Int) {
if (left != indicatorLeft || right != indicatorRight) {
// If the indicator's left/right hasBackStack changed, invalidate
indicatorLeft = left
indicatorRight = right
viewIndicator.left = indicatorLeft
viewIndicator.right = indicatorRight
}
}
private fun animateIndicatorToPosition() {
if (indicatorAnimator != null && indicatorAnimator!!.isRunning) {
indicatorAnimator!!.cancel()
}
val targetLeft: Int = subInventoryText?.left
val targetRight: Int = subInventoryText?.right
val startLeft: Int = selectedText?.left
val startRight: Int = selectedText?.right
if (startLeft != targetLeft || startRight != targetRight) {
indicatorAnimator = ValueAnimator()
val animator = indicatorAnimator
animator?.interpolator = FastOutSlowInInterpolator()
animator?.duration = DEFAULT_TOGGLE_DURATION
animator?.setFloatValues(0F, 100F)
animator?.addUpdateListener { animator1 ->
val fraction = animator1.animatedFraction
setIndicatorPosition(
lerpValue(startLeft, targetLeft, fraction),
lerpValue(startRight, targetRight, fraction)
)
}
animator?.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animator: Animator) {
selectedText = subInventoryText
isIndicatorAnimatorEnd = true
}
})
animator?.start()
}
}
private fun lerpValue(startValue: Int, endValue: Int, fraction: Float): Int {
return startValue + (fraction * (endValue - startValue)).roundToInt()
}
}
#3 补充下:扩展实现Int.toDp()
fun Int.toDp(): Float = (this * Resources.getSystem().displayMetrics.density)
简单实现效果,到这就结束lo:)
源码附上:下载链接
=======================更新 2020-07-13=====================
添加设置默认选中位置/添加手动选中位置
//以下为添加代码
private var tabPosition: Int = 0
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
super.onLayout(changed, l, t, r, b)
//更新
updateSelectedTab()
}
//可设置显示默认位置
fun setSelectedTab(tabPosition: Int = 0) {
this.tabPosition = tabPosition
}
//设置显示位置
fun updateSelectedTab(position: Int = -1) {
if (position > -1 && tabPosition != position) {
this.tabPosition = position
}
when (tabPosition) {
0 -> {
subInventoryText = left_text
}
1 -> {
subInventoryText = mid_text
}
2 -> {
subInventoryText = right_text
}
else -> {
}
}
if (position == -1){
viewIndicator.left = subInventoryText?.left
viewIndicator.right = subInventoryText?.right
}else{
animateIndicatorToPosition()
}
toggleTabView()
}
同步更新至
Git地址: SlidingTabLayout