Android仿iOS菊花加载框

实现一个类似iOS的菊花加载框,效果如下:

主要方法是show(message: CharSequence, cancelable: Boolean = true),

message:加载的文本,可设为null或空字符串,设置为null或空字符串时就只显示菊花部分;

cancelable:点击外部区域是否可以取消加载框。

话不多说,直接上代码。

首先绘制菊花部分:

class LoadingView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    companion object {
        private val DEFAULT_PETAL_COLOR = Color.WHITE
        private val DEFAULT_PETAL_LENGTH = SizeUtil.dp2px(4.5f)
        private val DEFAULT_PETAL_WIDTH = SizeUtil.dp2px(2f)
        private val DEFAULT_PETAL_COUNT = 12
    }

    var petalColor: Int = DEFAULT_PETAL_COLOR      //花瓣颜色
    var petalLength: Float = DEFAULT_PETAL_LENGTH  //花瓣长度
    var petalWidth: Float = DEFAULT_PETAL_WIDTH    //花瓣宽度
    var petalCount: Int = DEFAULT_PETAL_COUNT      //花瓣个数

    private lateinit var mPaint: Paint
    private lateinit var mAnimator: ValueAnimator
    private var mCenterX: Float = 0f
    private var mCenterY: Float = 0f
    private var mCurrentIndex = 1

    init {
        attrs?.let {
            parseAttribute(getContext(), it)
        }
        initPaint()
    }

    //获取布局属性并设置属性默认值
    private fun parseAttribute(context: Context, attrs: AttributeSet) {
        val ta = context.obtainStyledAttributes(attrs, R.styleable.LoadingView)
        petalColor = ta.getColor(R.styleable.LoadingView_petalColor, DEFAULT_PETAL_COLOR)
        petalLength = ta.getDimension(R.styleable.LoadingView_petalLength, DEFAULT_PETAL_LENGTH)
        petalWidth = ta.getDimension(R.styleable.LoadingView_petalWidth, DEFAULT_PETAL_WIDTH)
        petalCount = ta.getInteger(R.styleable.LoadingView_petalCount, DEFAULT_PETAL_COUNT)
        ta.recycle()
    }

    //初始化画笔
    private fun initPaint() {
        mPaint = Paint()
        with(mPaint) {
            isAntiAlias = true
            strokeCap = Paint.Cap.ROUND
            style = Paint.Style.STROKE
            color = petalColor
            strokeWidth = petalWidth
        }
    }

    //重写onMeasure支持wrap_content
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        setMeasuredDimension(measure(widthMeasureSpec), measure(heightMeasureSpec))
    }

    private fun measure(measureSpec: Int): Int {
        var result = 0
        val specMode = MeasureSpec.getMode(measureSpec)
        val specSize = MeasureSpec.getSize(measureSpec)
        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize
        } else {
            result = SizeUtil.dp2px(30f).toInt()
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize)
            }
        }
        return result
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        mCenterX = measuredWidth / 2f
        mCenterY = measuredHeight / 2f
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        canvas?.let {
            for (i in 0..petalCount - 1) {
                mPaint.alpha = (i + 1 + mCurrentIndex) % petalCount * 255 / petalCount
                it.drawLine(mCenterX, petalWidth / 2f + 1, mCenterX, petalLength + petalWidth / 2f + 1, mPaint)
                it.rotate(360f / petalCount, mCenterX, mCenterY)
            }
        }
    }

    override fun onAttachedToWindow() {
        super.onAttachedToWindow()
        mAnimator = ValueAnimator.ofInt(petalCount, 1)
        with(mAnimator) {
            duration = 1000
            interpolator = LinearInterpolator()
            repeatCount = ValueAnimator.INFINITE
            repeatMode = ValueAnimator.RESTART
            addUpdateListener { animation ->
                mCurrentIndex = animation.animatedValue as Int
                invalidate()
            }
            start()
        }
    }

    override fun onDetachedFromWindow() {
        mAnimator.cancel()
        super.onDetachedFromWindow()
    }

}

有四个自定义属性:花瓣颜色、花瓣个数、花瓣长度、花瓣宽度,在attrs.xml中定义如下:

<!--仿iOS菊花加载-->
<declare-styleable name="LoadingView">
    <attr name="petalColor" format="color" />
    <attr name="petalLength" format="dimension" />
    <attr name="petalWidth" format="dimension" />
    <attr name="petalCount" format="integer" />
</declare-styleable>

 然后开始绘制加载框部分:

class LoadingDialog @JvmOverloads constructor(
    context: Context, themeStyle: Int = R.style.LoadingDialogStyle
) : Dialog(context, themeStyle) {

    init {
        setContentView(R.layout.dialog_loading)
    }

    //显示加载框,默认可取消
    fun show(message: CharSequence, cancelable: Boolean = true) {
        with(findViewById<TextView>(R.id.loading_tv)) {
            if (TextUtils.isEmpty(message)) {
                visibility = View.GONE
            } else {
                visibility = View.VISIBLE
                text = message
            }
        }
        //点击对话框外的部分不消失
        setCanceledOnTouchOutside(cancelable)
        //点击或按返回键时是否消失
        setCancelable(cancelable)
        show()
    }

}

dialog_loading布局如下:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tool="http://schemas.android.com/tools"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <LinearLayout
        android:layout_width="90dp"
        android:layout_height="90dp"
        android:background="@drawable/shape_loading_dialog_bg"
        android:gravity="center"
        android:orientation="vertical">

        <com.android.widget.LoadingView
            android:id="@+id/loading_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <TextView
            android:id="@+id/loading_tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:includeFontPadding="false"
            android:textColor="@color/white"
            android:textSize="13sp"
            android:visibility="gone"
            tool:text="加载中..." />

    </LinearLayout>

</FrameLayout>

 LoadingDialogStyle在styles.xml中定义如下:

<!--对话框样式-->
<style name="DialogStyle" parent="@android:style/Theme.Dialog">
    <item name="android:windowFrame">@null</item>
    <item name="android:windowContentOverlay">@null</item>
    <item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
    <item name="android:windowSoftInputMode">stateUnspecified|adjustPan</item>
    <item name="android:windowBackground">@android:color/transparent</item>
    <item name="android:windowNoTitle">true</item>
    <item name="android:windowIsFloating">true</item>
</style>

<!--加载框样式-->
<style name="LoadingDialogStyle" parent="DialogStyle">
    <item name="android:backgroundDimEnabled">false</item>  <!--activity不变暗-->
</style>

shape_loading_dialog_bg的代码:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#CC1F2735" />
    <corners android:radius="10dp" />
</shape>

使用如下:

//初始化
val loadingDialog = LoadingDialog(this, R.style.LoadingDialogStyle)
//显示加载框
loadingDialog.show(message, cancelable)
//销毁加载框
loadingDialog.dismiss()

附完整代码

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值