实现一个类似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()