|
@@ -0,0 +1,323 @@
|
|
|
|
|
+package io.legado.app.ui.widget.text
|
|
|
|
|
+
|
|
|
|
|
+import android.content.Context
|
|
|
|
|
+import android.graphics.Canvas
|
|
|
|
|
+import android.graphics.Color
|
|
|
|
|
+import android.graphics.Paint
|
|
|
|
|
+import android.graphics.Path
|
|
|
|
|
+import android.util.AttributeSet
|
|
|
|
|
+import android.util.TypedValue
|
|
|
|
|
+import android.view.View
|
|
|
|
|
+import io.legado.app.R
|
|
|
|
|
+import io.legado.app.lib.theme.accentColor
|
|
|
|
|
+
|
|
|
|
|
+@Suppress("unused", "PrivatePropertyName")
|
|
|
|
|
+class BevelLabelView @JvmOverloads constructor(
|
|
|
|
|
+ context: Context,
|
|
|
|
|
+ attrs: AttributeSet? = null
|
|
|
|
|
+) : View(context, attrs) {
|
|
|
|
|
+
|
|
|
|
|
+ private val MODE_LEFT_TOP = 0
|
|
|
|
|
+ private val MODE_RIGHT_TOP = 1
|
|
|
|
|
+ private val MODE_LEFT_BOTTOM = 2
|
|
|
|
|
+ private val MODE_RIGHT_BOTTOM = 3
|
|
|
|
|
+ private val MODE_LEFT_TOP_FILL = 4
|
|
|
|
|
+ private val MODE_RIGHT_TOP_FILL = 5
|
|
|
|
|
+ private val MODE_LEFT_BOTTOM_FILL = 6
|
|
|
|
|
+ private val MODE_RIGHT_BOTTOM_FILL = 7
|
|
|
|
|
+
|
|
|
|
|
+ private var mBgColor: Int
|
|
|
|
|
+ private var mText: String
|
|
|
|
|
+ private var mTextSize: Int
|
|
|
|
|
+ private var mTextColor: Int
|
|
|
|
|
+ private var mLength: Int
|
|
|
|
|
+ private var mCorner: Int
|
|
|
|
|
+ private var mMode: Int
|
|
|
|
|
+ private var mPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG)
|
|
|
|
|
+ private var path: Path = Path()
|
|
|
|
|
+ private var mWidth = 0
|
|
|
|
|
+ private var mHeight: Int = 0
|
|
|
|
|
+ private var mRotate = 45 //因为默认模式是1,所以这时是45度
|
|
|
|
|
+
|
|
|
|
|
+ private var mX: Int = 0
|
|
|
|
|
+ private var mY: Int = 0
|
|
|
|
|
+
|
|
|
|
|
+ init {
|
|
|
|
|
+ val typedArray = context.obtainStyledAttributes(attrs, R.styleable.BevelLabelView)
|
|
|
|
|
+ mBgColor = typedArray.getColor(
|
|
|
|
|
+ R.styleable.BevelLabelView_label_bg_color,
|
|
|
|
|
+ context.accentColor
|
|
|
|
|
+ ) //默认红色
|
|
|
|
|
+ mText = typedArray.getString(R.styleable.BevelLabelView_label_text) ?: ""
|
|
|
|
|
+ mTextSize =
|
|
|
|
|
+ typedArray.getDimensionPixelOffset(
|
|
|
|
|
+ R.styleable.BevelLabelView_label_text_size,
|
|
|
|
|
+ sp2px(11)
|
|
|
|
|
+ )
|
|
|
|
|
+ mTextColor = typedArray.getColor(R.styleable.BevelLabelView_label_text_color, Color.WHITE)
|
|
|
|
|
+ mLength =
|
|
|
|
|
+ typedArray.getDimensionPixelOffset(R.styleable.BevelLabelView_label_length, dip2px(40))
|
|
|
|
|
+ mCorner = typedArray.getDimensionPixelOffset(R.styleable.BevelLabelView_label_corner, 0)
|
|
|
|
|
+ mMode = typedArray.getInt(R.styleable.BevelLabelView_label_mode, 1)
|
|
|
|
|
+ mPaint.isAntiAlias = true
|
|
|
|
|
+ typedArray.recycle()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
|
|
|
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
|
|
|
|
+ mWidth = MeasureSpec.getSize(widthMeasureSpec)
|
|
|
|
|
+ mHeight = mWidth
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ override fun onDraw(canvas: Canvas) {
|
|
|
|
|
+ mPaint.color = mBgColor
|
|
|
|
|
+ drawBackgroundText(canvas)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ private fun drawBackgroundText(canvas: Canvas) {
|
|
|
|
|
+ check(mWidth == mHeight) {
|
|
|
|
|
+ "width must equal to height" //标签view 是一个正方形,
|
|
|
|
|
+ }
|
|
|
|
|
+ when (mMode) {
|
|
|
|
|
+ MODE_LEFT_TOP -> {
|
|
|
|
|
+ mCorner = 0 //没有铺满的时候mCorner要归零;
|
|
|
|
|
+ leftTopMeasure()
|
|
|
|
|
+ getLeftTop()
|
|
|
|
|
+ }
|
|
|
|
|
+ MODE_RIGHT_TOP -> {
|
|
|
|
|
+ mCorner = 0
|
|
|
|
|
+ rightTopMeasure()
|
|
|
|
|
+ getRightTop()
|
|
|
|
|
+ }
|
|
|
|
|
+ MODE_LEFT_BOTTOM -> {
|
|
|
|
|
+ mCorner = 0
|
|
|
|
|
+ leftBottomMeasure()
|
|
|
|
|
+ getLeftBottom()
|
|
|
|
|
+ }
|
|
|
|
|
+ MODE_RIGHT_BOTTOM -> {
|
|
|
|
|
+ mCorner = 0
|
|
|
|
|
+ rightBottomMeasure()
|
|
|
|
|
+ getRightBottom()
|
|
|
|
|
+ }
|
|
|
|
|
+ MODE_LEFT_TOP_FILL -> {
|
|
|
|
|
+ leftTopMeasure()
|
|
|
|
|
+ getLeftTopFill()
|
|
|
|
|
+ if (mCorner != 0) {
|
|
|
|
|
+ canvas.drawPath(path, mPaint)
|
|
|
|
|
+ getLeftTop()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ MODE_RIGHT_TOP_FILL -> {
|
|
|
|
|
+ rightTopMeasure()
|
|
|
|
|
+ getRightTopFill()
|
|
|
|
|
+ if (mCorner != 0) {
|
|
|
|
|
+ canvas.drawPath(path, mPaint)
|
|
|
|
|
+ getRightTop()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ MODE_LEFT_BOTTOM_FILL -> {
|
|
|
|
|
+ leftBottomMeasure()
|
|
|
|
|
+ getLeftBottomFill()
|
|
|
|
|
+ if (mCorner != 0) {
|
|
|
|
|
+ canvas.drawPath(path, mPaint)
|
|
|
|
|
+ getLeftBottom()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ MODE_RIGHT_BOTTOM_FILL -> {
|
|
|
|
|
+ rightBottomMeasure()
|
|
|
|
|
+ getRightBottomFill()
|
|
|
|
|
+ if (mCorner != 0) {
|
|
|
|
|
+ canvas.drawPath(path, mPaint)
|
|
|
|
|
+ getRightBottom()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ else -> {}
|
|
|
|
|
+ }
|
|
|
|
|
+ canvas.drawPath(path, mPaint)
|
|
|
|
|
+ mPaint.textSize = mTextSize.toFloat()
|
|
|
|
|
+ mPaint.textAlign = Paint.Align.CENTER
|
|
|
|
|
+ mPaint.color = mTextColor
|
|
|
|
|
+ canvas.translate(mX.toFloat(), mY.toFloat())
|
|
|
|
|
+ canvas.rotate(mRotate.toFloat())
|
|
|
|
|
+ val baseLineY = (-(mPaint.descent() + mPaint.ascent())).toInt() / 2 //基线中间点的y轴计算公式
|
|
|
|
|
+ canvas.drawText(mText, 0f, baseLineY.toFloat(), mPaint)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private fun rightBottomMeasure() {
|
|
|
|
|
+ mRotate = -45
|
|
|
|
|
+ mX = mWidth / 2 + mLength / 4
|
|
|
|
|
+ mY = mX
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private fun leftBottomMeasure() {
|
|
|
|
|
+ mRotate = 45
|
|
|
|
|
+ mX = mWidth / 2 - mLength / 4
|
|
|
|
|
+ mY = mHeight / 2 + mLength / 4
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private fun rightTopMeasure() {
|
|
|
|
|
+ mRotate = 45
|
|
|
|
|
+ mX = mWidth / 2 + mLength / 4
|
|
|
|
|
+ mY = mHeight / 2 - mLength / 4
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private fun leftTopMeasure() {
|
|
|
|
|
+ mRotate = -45
|
|
|
|
|
+ mX = mWidth / 2 - mLength / 4
|
|
|
|
|
+ mY = mX
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ //左上角铺满
|
|
|
|
|
+ private fun getLeftTopFill() {
|
|
|
|
|
+ if (mCorner != 0) {
|
|
|
|
|
+ path.addRoundRect(
|
|
|
|
|
+ 0f,
|
|
|
|
|
+ 0f,
|
|
|
|
|
+ (mWidth / 2).toFloat(),
|
|
|
|
|
+ (mHeight / 2).toFloat(),
|
|
|
|
|
+ floatArrayOf(mCorner.toFloat(), mCorner.toFloat(), 0f, 0f, 0f, 0f, 0f, 0f),
|
|
|
|
|
+ Path.Direction.CW
|
|
|
|
|
+ )
|
|
|
|
|
+ } else {
|
|
|
|
|
+ path.moveTo(0f, 0f)
|
|
|
|
|
+ path.lineTo(mWidth.toFloat(), 0f)
|
|
|
|
|
+ path.lineTo(0f, mHeight.toFloat())
|
|
|
|
|
+ path.close()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ //左上角不铺满
|
|
|
|
|
+ private fun getLeftTop() {
|
|
|
|
|
+ path.moveTo(if (mCorner != 0) mCorner.toFloat() else (mWidth - mLength).toFloat(), 0f)
|
|
|
|
|
+ path.lineTo(mWidth.toFloat(), 0f)
|
|
|
|
|
+ path.lineTo(0f, mHeight.toFloat())
|
|
|
|
|
+ path.lineTo(0f, if (mCorner != 0) mCorner.toFloat() else (mHeight - mLength).toFloat())
|
|
|
|
|
+ path.close()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ //左下角铺满
|
|
|
|
|
+ private fun getLeftBottomFill() {
|
|
|
|
|
+ if (mCorner != 0) {
|
|
|
|
|
+ path.addRoundRect(
|
|
|
|
|
+ 0f,
|
|
|
|
|
+ (mHeight / 2).toFloat(),
|
|
|
|
|
+ (mWidth / 2).toFloat(),
|
|
|
|
|
+ mHeight.toFloat(),
|
|
|
|
|
+ floatArrayOf(0f, 0f, 0f, 0f, 0f, 0f, mCorner.toFloat(), mCorner.toFloat()),
|
|
|
|
|
+ Path.Direction.CW
|
|
|
|
|
+ )
|
|
|
|
|
+ } else {
|
|
|
|
|
+ path.moveTo(0f, 0f)
|
|
|
|
|
+ path.lineTo(mWidth.toFloat(), mHeight.toFloat())
|
|
|
|
|
+ path.lineTo(0f, mHeight.toFloat())
|
|
|
|
|
+ path.close()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ //左下角不铺满
|
|
|
|
|
+ private fun getLeftBottom() {
|
|
|
|
|
+ path.moveTo(0f, 0f)
|
|
|
|
|
+ path.lineTo(mWidth.toFloat(), mHeight.toFloat())
|
|
|
|
|
+ path.lineTo(
|
|
|
|
|
+ if (mCorner != 0) mCorner.toFloat() else (mWidth - mLength).toFloat(),
|
|
|
|
|
+ mHeight.toFloat()
|
|
|
|
|
+ )
|
|
|
|
|
+ path.lineTo(0f, if (mCorner != 0) (mHeight - mCorner).toFloat() else mLength.toFloat())
|
|
|
|
|
+ path.close()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ //右上角铺满
|
|
|
|
|
+ private fun getRightTopFill() {
|
|
|
|
|
+ if (mCorner != 0) {
|
|
|
|
|
+ path.addRoundRect(
|
|
|
|
|
+ (mWidth / 2).toFloat(),
|
|
|
|
|
+ 0f,
|
|
|
|
|
+ mWidth.toFloat(),
|
|
|
|
|
+ (mHeight / 2).toFloat(),
|
|
|
|
|
+ floatArrayOf(0f, 0f, mCorner.toFloat(), mCorner.toFloat(), 0f, 0f, 0f, 0f),
|
|
|
|
|
+ Path.Direction.CW
|
|
|
|
|
+ )
|
|
|
|
|
+ } else {
|
|
|
|
|
+ path.moveTo(0f, 0f)
|
|
|
|
|
+ path.lineTo(mWidth.toFloat(), 0f)
|
|
|
|
|
+ path.lineTo(mWidth.toFloat(), mHeight.toFloat())
|
|
|
|
|
+ path.close()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ //右上角不铺满
|
|
|
|
|
+ private fun getRightTop() {
|
|
|
|
|
+ path.moveTo(0f, 0f)
|
|
|
|
|
+ path.lineTo(if (mCorner != 0) (mWidth - mCorner).toFloat() else mLength.toFloat(), 0f)
|
|
|
|
|
+ path.lineTo(
|
|
|
|
|
+ mWidth.toFloat(),
|
|
|
|
|
+ if (mCorner != 0) mCorner.toFloat() else (mHeight - mLength).toFloat()
|
|
|
|
|
+ )
|
|
|
|
|
+ path.lineTo(mWidth.toFloat(), mHeight.toFloat())
|
|
|
|
|
+ path.close()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ //右下角铺满
|
|
|
|
|
+ private fun getRightBottomFill() {
|
|
|
|
|
+ if (mCorner != 0) {
|
|
|
|
|
+ path.addRoundRect(
|
|
|
|
|
+ (mWidth / 2).toFloat(),
|
|
|
|
|
+ (mHeight / 2).toFloat(),
|
|
|
|
|
+ mWidth.toFloat(),
|
|
|
|
|
+ mHeight.toFloat(),
|
|
|
|
|
+ floatArrayOf(0f, 0f, 0f, 0f, mCorner.toFloat(), mCorner.toFloat(), 0f, 0f),
|
|
|
|
|
+ Path.Direction.CW
|
|
|
|
|
+ )
|
|
|
|
|
+ } else {
|
|
|
|
|
+ path.moveTo(mWidth.toFloat(), 0f)
|
|
|
|
|
+ path.lineTo(mWidth.toFloat(), mHeight.toFloat())
|
|
|
|
|
+ path.lineTo(0f, mHeight.toFloat())
|
|
|
|
|
+ path.close()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ //右下角不铺满
|
|
|
|
|
+ private fun getRightBottom() {
|
|
|
|
|
+ path.moveTo(mWidth.toFloat(), 0f)
|
|
|
|
|
+ path.lineTo(
|
|
|
|
|
+ mWidth.toFloat(),
|
|
|
|
|
+ if (mCorner != 0) (mHeight - mCorner).toFloat() else mLength.toFloat()
|
|
|
|
|
+ )
|
|
|
|
|
+ path.lineTo(
|
|
|
|
|
+ if (mCorner != 0) (mWidth - mCorner).toFloat() else mLength.toFloat(),
|
|
|
|
|
+ mHeight.toFloat()
|
|
|
|
|
+ )
|
|
|
|
|
+ path.lineTo(0f, mHeight.toFloat())
|
|
|
|
|
+ path.close()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @param sp 转换大小
|
|
|
|
|
+ */
|
|
|
|
|
+ @Suppress("SameParameterValue")
|
|
|
|
|
+ private fun sp2px(sp: Int): Int {
|
|
|
|
|
+ return TypedValue.applyDimension(
|
|
|
|
|
+ TypedValue.COMPLEX_UNIT_SP,
|
|
|
|
|
+ sp.toFloat(),
|
|
|
|
|
+ resources.displayMetrics
|
|
|
|
|
+ )
|
|
|
|
|
+ .toInt()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @param dip 转换大小
|
|
|
|
|
+ */
|
|
|
|
|
+ @Suppress("SameParameterValue")
|
|
|
|
|
+ private fun dip2px(dip: Int): Int {
|
|
|
|
|
+ return TypedValue.applyDimension(
|
|
|
|
|
+ TypedValue.COMPLEX_UNIT_DIP,
|
|
|
|
|
+ dip.toFloat(),
|
|
|
|
|
+ resources.displayMetrics
|
|
|
|
|
+ )
|
|
|
|
|
+ .toInt()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+}
|