安卓开发中自定义View变色导航的实现

 前情提要

这个Demo是照搬博主红橙Darren所实现的,作为第一个自定义View学习使用(博主是用Java编写的,我是用Kotlin实现的)

自定义View的实现根据以下步骤处理:

  1. 分析需求
  2. 确定自定义属性(编写attrs.xml资源文件)
  3. 在自定义View中获取自定义属性
  4. 在布局中使用,绘制

下面提供实现效果和所有源码和部分注释(自己抄了一遍也是迷迷瞪瞪的)

 

1、自定义View运行效果

效果一:编写的变色自定义View(ColorTrackTextView)实现效果如下

 

 

 2、使用该View实现变色导航效果

效果二:

使用该View实现变色导航运行效果如下:

 

 

正文开始: 

【1】实现效果一

1、编写自定义属性

创建attrs.xml文件设置属性

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="ColorTrackTextView">
        <attr name="originColor" format="color"/>
        <attr name="changeColor" format="color"/>
    </declare-styleable>
</resources>

 


 

2、创建自定义View

创建自定义View(ColorTrackTextView)

package com.example.myapplicationchangetextcolor

import android.annotation.SuppressLint
import android.content.ContentValues
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Paint.FontMetricsInt
import android.graphics.Rect
import android.util.AttributeSet
import android.util.Log
import androidx.appcompat.widget.AppCompatTextView


@SuppressLint("Recycle")
class ColorTrackTextView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : AppCompatTextView(context, attrs, defStyleAttr) {
    //创建画笔
    private var mOriginPaint = Paint()
    private var mChangePaint = Paint()
    //设置进度
    private var mCurrentProgress = 0f

    //枚举类实现设置不同朝向
    enum class Direction{
        LEFT_TO_RIGHT, RIGHT_TO_LEFT
    }
    //设置初始朝向
    private var mDirection:Direction = Direction.LEFT_TO_RIGHT

    init {
        // 获取自定义属性
        val typedArray = context.theme.obtainStyledAttributes(
            attrs,
            R.styleable.ColorTrackTextView,
            0, 0
        )
        with(typedArray) {
            try {
                // 使用资源ID获取自定义属性值
                val originColor = getColor(R.styleable.ColorTrackTextView_originColor, Color.BLACK)
                val changeColor = getColor(R.styleable.ColorTrackTextView_changeColor, Color.RED)
                //设置画笔
                mOriginPaint = getPaintByColor(originColor)
                mChangePaint = getPaintByColor(changeColor)

            } finally {
                // 调用 recycle 释放资源
                recycle()
            }
        }
    }

    override fun onDraw(canvas: Canvas) {

        val middle:Int = (mCurrentProgress*getWidth()).toInt()

        if(mDirection == Direction.LEFT_TO_RIGHT){
            DrawText(canvas,mChangePaint,0,middle)
            DrawText(canvas,mOriginPaint,middle,getWidth())
        }else{
            DrawText(canvas,mChangePaint,getWidth()-middle,getWidth())
            DrawText(canvas,mOriginPaint,0,getWidth()-middle)
        }

    }

    fun setDirection(direction: Direction){
        this.mDirection = direction
    }

    fun setCurrentProgress(currentProgress:Float){
        this.mCurrentProgress = currentProgress
        Log.e(ContentValues.TAG, "Now currentProgress is $currentProgress")

        invalidate()
    }

    private fun DrawText(canvas:Canvas,paint:Paint,start:Int,end:Int){
        canvas.save()
        // 绘制不变色
        val rect_current = Rect(start,0,end,getHeight())
        canvas.clipRect(rect_current)
        val text = getText().toString()
        val bounds = Rect()
        paint.getTextBounds(text, 0, text.length, bounds)
        val dx = getWidth() / 2 - bounds.width() / 2

        val fontMetricsInt: FontMetricsInt = paint.getFontMetricsInt()
        val dy = (fontMetricsInt.bottom - fontMetricsInt.top) / 2 - fontMetricsInt.bottom
        val baseLine = getHeight() / 2 + dy
        canvas.drawText(text, dx.toFloat(), baseLine.toFloat(), paint)
        canvas.restore()

    }


    private fun getPaintByColor(mColor: Int): Paint {
        val paint = Paint().apply {
            // 设置颜色
            color = mColor
            // 设置抗锯齿
            isAntiAlias = true
            //  textSize = TextView.textSize 获取不到设置字体大小,报错:Unresolved reference: textSize

            //this@ColorTrackTextView 被用来引用当前的 ColorTrackTextView 实例。
            // this 关键字是指向当前类实例的引用,而 @ColorTrackTextView 是一个限定符,
            // 用来明确指出 this 属于 ColorTrackTextView 类。
            textSize = this@ColorTrackTextView.textSize
            isFakeBoldText = true
        }
        return paint
    }

    fun setChangeColor(changeColor:Int) {
        this.mChangePaint.setColor(changeColor)
    }

    fun setOriginColor(originColor:Int) {
        this.mOriginPaint.setColor(originColor)
    }


}

 


 

3、编写Activity_main.xml 

Activity_main.xml 文件布局是实现的是效果一的页面布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    xmlns:tool="http://schemas.android.com/tools"
    android:layout_margin="30dp"
    android:paddingLeft="16dp"
    android:paddingTop="16dp"
    android:paddingRight="16dp"
    android:paddingBottom="16dp"
    tool:context="com.example.myapplicationchangetextcolor.MainActivity">

    <com.example.myapplicationchangetextcolor.ColorTrackTextView
        android:id="@+id/color_track_tv"
        app:originColor="@color/black"
        app:changeColor="#F44336"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Look the Fuck Resource Code!"
        android:textStyle="bold"
        android:textSize="26sp"/>
   <LinearLayout
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:orientation="horizontal">
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="leftToRight"
            android:text="从左到右"
            android:layout_gravity="start"
            tool:ignore="OnClick" />
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="rightToLeft"
            android:text="从右到左"
            tool:ignore="OnClick" />
   </LinearLayout>



</LinearLayout>

 


 

4、编辑MainActivity

编辑MainActivity文件,获取控件实现动画效果

package com.example.myapplicationchangetextcolor

import android.animation.ObjectAnimator
import android.os.Bundle
import android.view.View
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {
    private lateinit var mColorTrackTextView:ColorTrackTextView
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContentView(R.layout.activity_main)
        mColorTrackTextView = findViewById(R.id.color_track_tv)


    }
    //启动动画效果
    fun leftToRight(view: View){
        //设置动画方向
        mColorTrackTextView.setDirection(ColorTrackTextView.Direction.LEFT_TO_RIGHT)
        //从0到1的浮点数值动画是指动画的属性值从一个起始值(0)变化到一个结束值(1)。
        //在这个过程中,动画的值会从0.0开始,逐渐增加,直到达到1.0。
        //这种类型的动画通常用于表示某种进度或变化
        val valueAnimator = ObjectAnimator.ofFloat(0f,1f).apply {
            duration = 2000
            //添加一个更新监听器 addUpdateListener,它将在动画的每次更新时被调用
            addUpdateListener { animation->
                //获取当前动画的值
                val currentProgress = animation.animatedValue as Float
                //调用 mColorTrackTextView 的 setCurrentProgress 方法,
                // 传入 currentProgress 作为参数,用于更新视图的进度状态
                mColorTrackTextView.setCurrentProgress(currentProgress)

            }
        }

        valueAnimator.start()
    }
    //同上
    fun rightToLeft(view: View){
        mColorTrackTextView.setDirection(ColorTrackTextView.Direction.RIGHT_TO_LEFT)

        val valueAnimator = ObjectAnimator.ofFloat(0f,1f).apply {
            duration = 2000
            addUpdateListener { animation->
                val currentProgress = animation.animatedValue as Float
                mColorTrackTextView.setCurrentProgress(currentProgress)
            }
        }
        valueAnimator.start()
    }

}

 


 

 【2】实现效果二

1、创建ItemFragment

package com.example.myapplicationchangetextcolor

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.Fragment

class ItemFragment : Fragment() {


    // 其他代码...

    companion object {
        // 使用@JvmStatic注解确保可以从Java代码中调用这个静态方法
        @JvmStatic
        fun newInstance(item: String): ItemFragment {
            val itemFragment = ItemFragment()
            val bundle = Bundle()
            bundle.putString("title", item) // 假设"title"是您想要传递的键名
            itemFragment.arguments = bundle
            return itemFragment
        }
    }


    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // 使用LayoutInflater来加载布局
        val view = inflater.inflate(R.layout.fragment_item, container, false)

        // 通过ID查找TextView,并将其转换为TextView类型
        val tv: TextView = view.findViewById(R.id.text)

        // 获取传递给Fragment的参数
        val bundle: Bundle? = arguments

        // 从Bundle中获取字符串并设置到TextView上
        bundle?.let {
            tv.text = it.getString("title")
        }

        // 返回加载的视图
        return view


    }


}

 


2、创建对应的fragment_item

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/text"
        android:text="Hello"
        android:gravity="center"/>

</LinearLayout>

 


 

 3、创建ViewPagerActivity

package com.example.myapplicationchangetextcolor

import android.content.ContentValues.TAG
import android.graphics.Color
import android.os.Bundle
import android.util.Log
import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentPagerAdapter
import androidx.viewpager.widget.ViewPager

class ViewPagerActivity : AppCompatActivity() {

    // 定义一个字符串数组
    private val items: Array<String> = arrayOf("直播", "推荐", "视频", "图片", "段子", "精华")

    // 定义LinearLayout类型的成员变量,用于存放指示器容器
    private lateinit var mIndicatorContainer: LinearLayout

    // 定义List<ColorTrackTextView>类型的成员变量,用于存放指示器
    private lateinit var mIndicators: MutableList<ColorTrackTextView>

    // 定义ViewPager类型的成员变量
    private lateinit var mViewPager: ViewPager
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContentView(R.layout.activity_view_pager)
        // 初始化指示器列表
        mIndicators = ArrayList()

        // 通过findViewById获取LinearLayout和ViewPager,并进行类型转换
        mIndicatorContainer = findViewById(R.id.indicator_view)
        mViewPager = findViewById(R.id.view_pager)

        // 调用方法初始化指示器和ViewPager
        initIndicator()
        initViewPager()

    }
    //初始化一个 ViewPager 组件,设置其适配器和页面滚动监听器,以及配置页面指示器的行为
    private fun initViewPager() {
        // 为mViewPager设置适配器,使用匿名对象继承自FragmentPagerAdapter
        mViewPager.adapter = object : FragmentPagerAdapter(supportFragmentManager) {
            override fun getItem(position: Int): Fragment {
                // 根据当前position返回一个新的ItemFragment实例
                return ItemFragment.newInstance(items[position])
            }

            override fun getCount(): Int {
                // 返回items数组的大小
                return items.size
            }

            override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
                // 空实现,如果不需要可以省略
            }
        }
        // 为mViewPager添加页面滚动变化监听器
        mViewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
             重写onPageScrolled方法,处理页面滚动时的事件
            override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
                // 打印日志,显示当前页面位置和位置偏移量
                Log.e(TAG, "position --> $position , positionOffset --> $positionOffset")
                // 当positionOffset大于0,即页面开始滑动时
                if (positionOffset > 0) {
                    // 获取当前位置的指示器,并设置其滚动方向和进度
                    val left = mIndicators[position]
                    left.setDirection(ColorTrackTextView.Direction.RIGHT_TO_LEFT)
                    left.setCurrentProgress(1 - positionOffset)

                    // 获取下一个位置的指示器,并设置其滚动方向和进度
                    val right = mIndicators[position + 1]
                    right.setDirection(ColorTrackTextView.Direction.LEFT_TO_RIGHT)
                    right.setCurrentProgress(positionOffset)


                }
            }

            override fun onPageSelected(position: Int) {
                // 可以在这里处理页面选中事件
            }

            override fun onPageScrollStateChanged(state: Int) {
                // 可以在这里处理滚动状态改变事件
            }
        })


    }

    //初始化一个页面指示器界面元素集合,并配置每个指示器的属性
    private fun initIndicator() {
        //用于存储 ColorTrackTextView 的实例
        mIndicators = mutableListOf()
        for (i in items.indices) {
            //为每个索引创建一个新的 ColorTrackTextView 实例
            val colorTrackTextView = ColorTrackTextView(this)
            colorTrackTextView.textSize = 20f
            colorTrackTextView.setOriginColor(Color.BLACK)
            colorTrackTextView.setChangeColor(Color.RED)
            colorTrackTextView.text = items[i]
            //用于设置和添加页面指示器(ColorTrackTextView)
            // 到一个线性布局容器(mIndicatorContainer)中
            val params = LinearLayout.LayoutParams(

                // ViewGroup.LayoutParams.WRAP_CONTENT子视图的大小将根据其内容自动调整
                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT
            ).apply {
                weight = 1f
            }
            //应用布局参数到 ColorTrackTextView
            colorTrackTextView.layoutParams = params
            //将配置好的 colorTrackTextView 添加到 mIndicatorContainer 中
            mIndicatorContainer.addView(colorTrackTextView)
            //将 colorTrackTextView 实例添加到 mIndicators 列表中
            mIndicators.add(colorTrackTextView)
        }
    }
}

 


 

 4、创建对应的activity_view_pager

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical"
    android:layout_marginTop="30dp">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:paddingTop="10dp"
        android:paddingBottom="10dp"
        android:id="@+id/indicator_view"/>

    <androidx.viewpager.widget.ViewPager
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:id="@+id/view_pager"
        android:layout_weight="1"/>

  </LinearLayout>

 


 

5、修改AndroidManifest文件

修改AndroidManifest文件运行即可,大功告成

        <activity
            android:name=".ViewPagerActivity"
            android:exported="true" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

  • 14
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值