自定义View之条状佛珠

该文章描述了一个名为LinearBead的自定义View,它在Android中使用手势检测和动画实现了一种线性佛珠滚动的效果。View通过处理Bitmap和Animator来绘制和移动佛珠,同时支持设置佛珠的数量和样式。当用户执行向上的fling手势时,佛珠会沿着线性路径滚动。
摘要由CSDN通过智能技术生成
package com.fenghuajueli.module_host.widget

import android.animation.Animator
import android.animation.Animator.AnimatorListener
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Matrix
import android.graphics.Paint
import android.graphics.Rect
import android.util.AttributeSet
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
import com.blankj.utilcode.util.LogUtils
import com.fenghuajueli.module_host.R
import kotlin.math.abs


class LinearBead(context: Context,attr: AttributeSet): View(context,attr) ,
        GestureDetector.OnGestureListener{
    private var mBeadNumber = 6
    private var mBeadStyle = arrayOf(
            R.mipmap.fz_6,
            R.mipmap.fz_7,
            R.mipmap.fz_8,
            R.mipmap.fz_9,
            R.mipmap.fz_10,
            R.mipmap.fz_11,
    )
    private lateinit var mBitPaint: Paint
    private lateinit var mRopeBitmap:Bitmap
    private var mBeadBitmaps: MutableList<Bitmap> = arrayListOf()
    private var mBeadBitmapSize = 346
    private var mBeadsGap = 25

    private lateinit var mDetector: GestureDetector
    private lateinit var mValueAnimator: ValueAnimator
    private var isPlayingAnm: Boolean = false
    private var mTranslateY = 0f
    private lateinit var iIOnFlingListener: IOnFlingListener
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
//        LogUtils.e("onDraw")
        canvas.drawBitmap(
                mRopeBitmap,
                Rect(0,0,mRopeBitmap.width,mRopeBitmap.height),
                Rect(
                        width / 2 - mRopeBitmap.width / 2,
                        0,
                        width / 2 + mRopeBitmap.width / 2,
                        mRopeBitmap.height
                ),
                mBitPaint
        )
        for (i in 0 until mBeadNumber + 1) {
            canvas.drawBitmap(
                    mBeadBitmaps[i],
                    Rect(0,0,mBeadBitmapSize,mBeadBitmapSize),
                    Rect(
                            width / 2 - mBeadBitmapSize / 2 ,
                            (mBeadsGap * (i+1) + mBeadBitmapSize * i - mTranslateY).toInt(),
                            width / 2 + mBeadBitmapSize / 2 ,
                            (mBeadsGap * (i+1) + mBeadBitmapSize * i - mTranslateY
                                    + mBeadBitmapSize).toInt()
                    ),
                    mBitPaint
            )
        }
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        LogUtils.e("onSizeChanged",w,h,oldw,oldh,height,measuredHeight)
        mDetector = GestureDetector(context,this)
        mRopeBitmap = BitmapFactory.decodeResource(resources,R.mipmap.img_zhulian)
                .copy(Bitmap.Config.ARGB_8888, true)
        mRopeBitmap.height = h
        initBitmapSize()
        initBeadBitmaps()
        initValueAnimator()
        initPaint()
    }

    /**
     * 初始化佛珠图
     */
    private fun initBeadBitmaps(){
        mBeadBitmaps.clear()
        var j = 0
        // 需增加屏幕底部外一颗珠子,方便动画移动显示 所以 加 1
        for (i in 0 until mBeadNumber + 1) {
            if (j == mBeadStyle.size) {
                j = 0
            }

            val bitmap = BitmapFactory.decodeResource(resources, mBeadStyle[j])
            mBeadBitmaps.add(imageScale(bitmap, mBeadBitmapSize,mBeadBitmapSize))
            j ++
        }
    }

    /**
     * (高度 - 每个珠子的间距) / 珠子数量 = 每个珠子的大小
     */
    private fun initBitmapSize(){
        // 25代表10dp
        mBeadBitmapSize = (height - mBeadsGap * (mBeadNumber + 1)) / mBeadNumber
        LogUtils.e("mBitmapSize",mBeadBitmapSize)
    }

    /**
     * 初始化值动画
     */
    private fun initValueAnimator(){
        mValueAnimator = ValueAnimator.ofFloat(0f, mBeadBitmapSize + mBeadsGap.toFloat())
        mValueAnimator.duration = 500
        mValueAnimator.addUpdateListener {
            mTranslateY = it.animatedValue as Float
            invalidate()
        }
        mValueAnimator.addListener(object :AnimatorListener{
            override fun onAnimationStart(p0: Animator) {
                isPlayingAnm = true
            }

            override fun onAnimationEnd(p0: Animator) {
                mTranslateY = 0f
                changeBeadBitmapsPosition()
                invalidate()
                isPlayingAnm = false
            }

            override fun onAnimationCancel(p0: Animator) {

            }

            override fun onAnimationRepeat(p0: Animator) {

            }

        })
    }

    /**
     * 改变珠子位置
     */
    private fun changeBeadBitmapsPosition() {
        var newBeadBitmaps: MutableList<Bitmap> = arrayListOf()
        for (i in 1 until mBeadBitmaps.size) {
            newBeadBitmaps.add(mBeadBitmaps[i])
        }
        newBeadBitmaps.add(mBeadBitmaps[1])
        mBeadBitmaps.clear()
        mBeadBitmaps.addAll(newBeadBitmaps)
    }

    private fun updateValueAnimator(){
        if (this::mValueAnimator.isInitialized){
            mValueAnimator.cancel()
        }
        initValueAnimator()
    }

    private fun initPaint(){
        mBitPaint = Paint(Paint.ANTI_ALIAS_FLAG)
        mBitPaint.isFilterBitmap = true
        mBitPaint.isDither = true
    }

    private fun imageScale(oldBitmap: Bitmap, newWidth: Int, newHeight: Int): Bitmap {
        //原始bitmap的宽高
        val oldWidth = oldBitmap.width
        val oldHeight = oldBitmap.height

        // 设置的宽高和原来的宽高的比例
        val scaleW = newWidth / oldWidth.toFloat()
        val scaleH = newHeight / oldHeight.toFloat()
        //比例
        val matrix = Matrix()
        matrix.postScale(scaleW, scaleH)
        return Bitmap.createBitmap(oldBitmap, 0, 0, oldWidth, oldHeight, matrix, true)
    }

    fun setBeadNumber(number: Int) {
        mBeadNumber = number
        initBitmapSize()
        initBeadBitmaps()
        invalidate()
        updateValueAnimator()
    }

    fun setBeadStyle(beadStyle: Array<Int>) {
        mBeadStyle = beadStyle
        initBeadBitmaps()
        invalidate()
    }

    fun setIOnFlingListener(listener: IOnFlingListener){
        iIOnFlingListener = listener
    }


    override fun onTouchEvent(event: MotionEvent): Boolean {
        mDetector.onTouchEvent(event)
        return true
    }

    override fun onDown(p0: MotionEvent): Boolean {
        return true
    }

    override fun onShowPress(p0: MotionEvent) {

    }

    override fun onSingleTapUp(p0: MotionEvent): Boolean {
        return false
    }

    override fun onScroll(p0: MotionEvent, p1: MotionEvent, p2: Float, p3: Float): Boolean {
        return false
    }

    override fun onLongPress(p0: MotionEvent) {

    }

    override fun onFling(e1: MotionEvent, e2: MotionEvent, p2: Float, p3: Float): Boolean {
        return if(e1.y - e2.y > 0 && abs((e1.y - e2.y)) > 30 && !isPlayingAnm){
            //向上滑动的判断,如果手指上滑动,就走这个方法
            mValueAnimator.start()
            iIOnFlingListener.slideUp()
            true
        }else {
            false
        }
    }

    interface IOnFlingListener{
        fun slideUp()
    }
}

需提供佛珠图片mBeadStyle

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
TypeArray 是 Android 中的一个特殊的资源类型,用于在 XML 中声明自定义 View 的属性。使用 TypeArray 可以方便地在 XML 布局中指定 View 的属性,而不需要在 Java 代码中进行硬编码。 使用 TypeArray 的步骤如下: 1. 在 res/values/attrs.xml 文件中定义自定义 View 的属性。 ```xml <resources> <declare-styleable name="MyCustomView"> <attr name="customAttr1" format="integer" /> <attr name="customAttr2" format="string" /> <attr name="customAttr3" format="boolean" /> </declare-styleable> </resources> ``` 2. 在自定义 View 的构造函数中获取 TypedArray 对象,并从中获取属性值。 ```java public class MyCustomView extends View { private int customAttr1; private String customAttr2; private boolean customAttr3; public MyCustomView(Context context, AttributeSet attrs) { super(context, attrs); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyCustomView); customAttr1 = a.getInt(R.styleable.MyCustomView_customAttr1, 0); customAttr2 = a.getString(R.styleable.MyCustomView_customAttr2); customAttr3 = a.getBoolean(R.styleable.MyCustomView_customAttr3, false); a.recycle(); } } ``` 在上面的代码中,`context.obtainStyledAttributes(attrs, R.styleable.MyCustomView)` 用于获取 TypedArray 对象,`R.styleable.MyCustomView` 是在 attrs.xml 文件中定义的自定义属性集合,`a.getInt()`、`a.getString()`、`a.getBoolean()` 用于从 TypedArray 对象中获取属性值,最后需要调用 `a.recycle()` 来回收 TypedArray 对象。 3. 在 XML 布局中使用自定义 View,并设置属性值。 ```xml <com.example.MyCustomView android:layout_width="match_parent" android:layout_height="wrap_content" app:customAttr1="123" app:customAttr2="hello" app:customAttr3="true" /> ``` 在上面的代码中,`app:customAttr1`、`app:customAttr2`、`app:customAttr3` 是在 attrs.xml 文件中定义的自定义属性名,可以在 XML 布局中使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值