pull/32/head
parent
b400ab884b
commit
2539b1a67f
@ -0,0 +1,233 @@ |
|||||||
|
package io.legado.app.ui.widget |
||||||
|
|
||||||
|
import android.content.Context |
||||||
|
import android.graphics.Color |
||||||
|
import android.graphics.drawable.ShapeDrawable |
||||||
|
import android.graphics.drawable.shapes.RoundRectShape |
||||||
|
import android.text.TextUtils |
||||||
|
import android.util.AttributeSet |
||||||
|
import android.util.TypedValue |
||||||
|
import android.view.Gravity |
||||||
|
import android.view.View |
||||||
|
import android.view.ViewGroup |
||||||
|
import android.widget.FrameLayout |
||||||
|
import android.widget.FrameLayout.LayoutParams |
||||||
|
import android.widget.TabWidget |
||||||
|
import androidx.appcompat.widget.AppCompatTextView |
||||||
|
import io.legado.app.R |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Created by milad heydari on 5/6/2016. |
||||||
|
*/ |
||||||
|
class BadgeView @JvmOverloads constructor( |
||||||
|
context: Context, |
||||||
|
attrs: AttributeSet? = null, |
||||||
|
defStyle: Int = android.R.attr.textViewStyle |
||||||
|
) : AppCompatTextView(context, attrs, defStyle) { |
||||||
|
|
||||||
|
/** |
||||||
|
* @return Returns true if view is hidden on badge value 0 or null; |
||||||
|
*/ |
||||||
|
/** |
||||||
|
* @param hideOnNull the hideOnNull to set |
||||||
|
*/ |
||||||
|
var isHideOnNull = true |
||||||
|
set(hideOnNull) { |
||||||
|
field = hideOnNull |
||||||
|
text = text |
||||||
|
} |
||||||
|
private var radius: Float = 0.toFloat() |
||||||
|
|
||||||
|
val badgeCount: Int? |
||||||
|
get() { |
||||||
|
if (text == null) { |
||||||
|
return null |
||||||
|
} |
||||||
|
val text = text.toString() |
||||||
|
try { |
||||||
|
return Integer.parseInt(text) |
||||||
|
} catch (e: NumberFormatException) { |
||||||
|
return null |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
var badgeGravity: Int |
||||||
|
get() { |
||||||
|
val params = layoutParams as LayoutParams |
||||||
|
return params.gravity |
||||||
|
} |
||||||
|
set(gravity) { |
||||||
|
val params = layoutParams as LayoutParams |
||||||
|
params.gravity = gravity |
||||||
|
layoutParams = params |
||||||
|
} |
||||||
|
|
||||||
|
val badgeMargin: IntArray |
||||||
|
get() { |
||||||
|
val params = layoutParams as LayoutParams |
||||||
|
return intArrayOf(params.leftMargin, params.topMargin, params.rightMargin, params.bottomMargin) |
||||||
|
} |
||||||
|
|
||||||
|
init { |
||||||
|
|
||||||
|
init() |
||||||
|
} |
||||||
|
|
||||||
|
private fun init() { |
||||||
|
if (layoutParams !is LayoutParams) { |
||||||
|
val layoutParams = LayoutParams( |
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT, |
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT, |
||||||
|
Gravity.CENTER |
||||||
|
) |
||||||
|
setLayoutParams(layoutParams) |
||||||
|
} |
||||||
|
|
||||||
|
// set default font |
||||||
|
setTextColor(Color.WHITE) |
||||||
|
//setTypeface(Typeface.DEFAULT_BOLD); |
||||||
|
setTextSize(TypedValue.COMPLEX_UNIT_SP, 11f) |
||||||
|
setPadding(dip2Px(5f), dip2Px(1f), dip2Px(5f), dip2Px(1f)) |
||||||
|
radius = 8f |
||||||
|
|
||||||
|
// set default background |
||||||
|
setBackground(radius, Color.parseColor("#d3321b")) |
||||||
|
|
||||||
|
gravity = Gravity.CENTER |
||||||
|
|
||||||
|
// default values |
||||||
|
isHideOnNull = true |
||||||
|
setBadgeCount(0) |
||||||
|
minWidth = dip2Px(16f) |
||||||
|
minHeight = dip2Px(16f) |
||||||
|
} |
||||||
|
|
||||||
|
fun setBackground(dipRadius: Float, badgeColor: Int) { |
||||||
|
val radius = dip2Px(dipRadius) |
||||||
|
val radiusArray = floatArrayOf( |
||||||
|
radius.toFloat(), |
||||||
|
radius.toFloat(), |
||||||
|
radius.toFloat(), |
||||||
|
radius.toFloat(), |
||||||
|
radius.toFloat(), |
||||||
|
radius.toFloat(), |
||||||
|
radius.toFloat(), |
||||||
|
radius.toFloat() |
||||||
|
) |
||||||
|
|
||||||
|
val roundRect = RoundRectShape(radiusArray, null, null) |
||||||
|
val bgDrawable = ShapeDrawable(roundRect) |
||||||
|
bgDrawable.paint.color = badgeColor |
||||||
|
background = bgDrawable |
||||||
|
} |
||||||
|
|
||||||
|
fun setBackground(badgeColor: Int) { |
||||||
|
setBackground(radius, badgeColor) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @see android.widget.TextView.setText |
||||||
|
*/ |
||||||
|
override fun setText(text: CharSequence, type: BufferType) { |
||||||
|
if (isHideOnNull && TextUtils.isEmpty(text)) { |
||||||
|
visibility = View.GONE |
||||||
|
} else { |
||||||
|
visibility = View.VISIBLE |
||||||
|
} |
||||||
|
super.setText(text, type) |
||||||
|
} |
||||||
|
|
||||||
|
fun setBadgeCount(count: Int) { |
||||||
|
text = count.toString() |
||||||
|
if (count == 0) { |
||||||
|
visibility = View.GONE |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun setHighlight(highlight: Boolean) { |
||||||
|
setBackground(resources.getColor(if (highlight) R.color.highlight else R.color.darker_gray)) |
||||||
|
} |
||||||
|
|
||||||
|
fun setBadgeMargin(dipMargin: Int) { |
||||||
|
setBadgeMargin(dipMargin, dipMargin, dipMargin, dipMargin) |
||||||
|
} |
||||||
|
|
||||||
|
fun setBadgeMargin(leftDipMargin: Int, topDipMargin: Int, rightDipMargin: Int, bottomDipMargin: Int) { |
||||||
|
val params = layoutParams as LayoutParams |
||||||
|
params.leftMargin = dip2Px(leftDipMargin.toFloat()) |
||||||
|
params.topMargin = dip2Px(topDipMargin.toFloat()) |
||||||
|
params.rightMargin = dip2Px(rightDipMargin.toFloat()) |
||||||
|
params.bottomMargin = dip2Px(bottomDipMargin.toFloat()) |
||||||
|
layoutParams = params |
||||||
|
} |
||||||
|
|
||||||
|
fun incrementBadgeCount(increment: Int) { |
||||||
|
val count = badgeCount |
||||||
|
if (count == null) { |
||||||
|
setBadgeCount(increment) |
||||||
|
} else { |
||||||
|
setBadgeCount(increment + count) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun decrementBadgeCount(decrement: Int) { |
||||||
|
incrementBadgeCount(-decrement) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Attach the BadgeView to the TabWidget |
||||||
|
* @param target the TabWidget to attach the BadgeView |
||||||
|
* @param tabIndex index of the tab |
||||||
|
*/ |
||||||
|
fun setTargetView(target: TabWidget, tabIndex: Int) { |
||||||
|
val tabView = target.getChildTabViewAt(tabIndex) |
||||||
|
setTargetView(tabView) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Attach the BadgeView to the target view |
||||||
|
* @param target the view to attach the BadgeView |
||||||
|
*/ |
||||||
|
fun setTargetView(target: View?) { |
||||||
|
if (parent != null) { |
||||||
|
(parent as ViewGroup).removeView(this) |
||||||
|
} |
||||||
|
|
||||||
|
if (target == null) { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
if (target.parent is FrameLayout) { |
||||||
|
(target.parent as FrameLayout).addView(this) |
||||||
|
|
||||||
|
} else if (target.parent is ViewGroup) { |
||||||
|
// use a new FrameLayout container for adding badge |
||||||
|
val parentContainer = target.parent as ViewGroup |
||||||
|
val groupIndex = parentContainer.indexOfChild(target) |
||||||
|
parentContainer.removeView(target) |
||||||
|
|
||||||
|
val badgeContainer = FrameLayout(context) |
||||||
|
val parentLayoutParams = target.layoutParams |
||||||
|
|
||||||
|
badgeContainer.layoutParams = parentLayoutParams |
||||||
|
target.layoutParams = ViewGroup.LayoutParams( |
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT |
||||||
|
) |
||||||
|
|
||||||
|
parentContainer.addView(badgeContainer, groupIndex, parentLayoutParams) |
||||||
|
badgeContainer.addView(target) |
||||||
|
|
||||||
|
badgeContainer.addView(this) |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* converts dip to px |
||||||
|
*/ |
||||||
|
private fun dip2Px(dip: Float): Int { |
||||||
|
return (dip * context.resources.displayMetrics.density + 0.5f).toInt() |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,207 @@ |
|||||||
|
package io.legado.app.ui.widget |
||||||
|
|
||||||
|
import android.animation.Animator |
||||||
|
import android.animation.AnimatorSet |
||||||
|
import android.animation.ObjectAnimator |
||||||
|
import android.content.Context |
||||||
|
import android.graphics.Canvas |
||||||
|
import android.graphics.Color |
||||||
|
import android.graphics.Paint |
||||||
|
import android.graphics.RectF |
||||||
|
import android.util.AttributeSet |
||||||
|
import android.util.TypedValue |
||||||
|
import android.view.View |
||||||
|
import android.view.animation.LinearInterpolator |
||||||
|
import io.legado.app.R |
||||||
|
|
||||||
|
/** |
||||||
|
* RotateLoading |
||||||
|
* Created by Victor on 2015/4/28. |
||||||
|
*/ |
||||||
|
class RotateLoading : View { |
||||||
|
|
||||||
|
private var mPaint: Paint? = null |
||||||
|
|
||||||
|
private var loadingRectF: RectF? = null |
||||||
|
private var shadowRectF: RectF? = null |
||||||
|
|
||||||
|
private var topDegree = 10 |
||||||
|
private var bottomDegree = 190 |
||||||
|
|
||||||
|
private var arc: Float = 0.toFloat() |
||||||
|
|
||||||
|
private var thisWidth: Int = 0 |
||||||
|
|
||||||
|
private var changeBigger = true |
||||||
|
|
||||||
|
private var shadowPosition: Int = 0 |
||||||
|
|
||||||
|
var isStart = false |
||||||
|
private set |
||||||
|
|
||||||
|
var loadingColor: Int = 0 |
||||||
|
|
||||||
|
private var speedOfDegree: Int = 0 |
||||||
|
|
||||||
|
private var speedOfArc: Float = 0.toFloat() |
||||||
|
|
||||||
|
constructor(context: Context) : super(context) { |
||||||
|
initView(context, null) |
||||||
|
} |
||||||
|
|
||||||
|
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { |
||||||
|
initView(context, attrs) |
||||||
|
} |
||||||
|
|
||||||
|
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { |
||||||
|
initView(context, attrs) |
||||||
|
} |
||||||
|
|
||||||
|
private fun initView(context: Context, attrs: AttributeSet?) { |
||||||
|
loadingColor = Color.WHITE |
||||||
|
thisWidth = dpToPx(context, DEFAULT_WIDTH.toFloat()) |
||||||
|
shadowPosition = dpToPx(getContext(), DEFAULT_SHADOW_POSITION.toFloat()) |
||||||
|
speedOfDegree = DEFAULT_SPEED_OF_DEGREE |
||||||
|
|
||||||
|
if (null != attrs) { |
||||||
|
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.RotateLoading) |
||||||
|
loadingColor = typedArray.getColor(R.styleable.RotateLoading_loading_color, Color.WHITE) |
||||||
|
thisWidth = typedArray.getDimensionPixelSize( |
||||||
|
R.styleable.RotateLoading_loading_width, |
||||||
|
dpToPx(context, DEFAULT_WIDTH.toFloat()) |
||||||
|
) |
||||||
|
shadowPosition = typedArray.getInt(R.styleable.RotateLoading_shadow_position, DEFAULT_SHADOW_POSITION) |
||||||
|
speedOfDegree = typedArray.getInt(R.styleable.RotateLoading_loading_speed, DEFAULT_SPEED_OF_DEGREE) |
||||||
|
typedArray.recycle() |
||||||
|
} |
||||||
|
speedOfArc = (speedOfDegree / 4).toFloat() |
||||||
|
mPaint = Paint() |
||||||
|
mPaint!!.color = loadingColor |
||||||
|
mPaint!!.isAntiAlias = true |
||||||
|
mPaint!!.style = Paint.Style.STROKE |
||||||
|
mPaint!!.strokeWidth = thisWidth.toFloat() |
||||||
|
mPaint!!.strokeCap = Paint.Cap.ROUND |
||||||
|
} |
||||||
|
|
||||||
|
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { |
||||||
|
super.onSizeChanged(w, h, oldw, oldh) |
||||||
|
|
||||||
|
arc = 10f |
||||||
|
|
||||||
|
loadingRectF = |
||||||
|
RectF((2 * thisWidth).toFloat(), (2 * thisWidth).toFloat(), (w - 2 * thisWidth).toFloat(), (h - 2 * thisWidth).toFloat()) |
||||||
|
shadowRectF = RectF( |
||||||
|
(2 * thisWidth + shadowPosition).toFloat(), |
||||||
|
(2 * thisWidth + shadowPosition).toFloat(), |
||||||
|
(w - 2 * thisWidth + shadowPosition).toFloat(), |
||||||
|
(h - 2 * thisWidth + shadowPosition).toFloat() |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
override fun onDraw(canvas: Canvas) { |
||||||
|
super.onDraw(canvas) |
||||||
|
|
||||||
|
if (!isStart) { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
mPaint!!.color = Color.parseColor("#1a000000") |
||||||
|
canvas.drawArc(shadowRectF!!, topDegree.toFloat(), arc, false, mPaint!!) |
||||||
|
canvas.drawArc(shadowRectF!!, bottomDegree.toFloat(), arc, false, mPaint!!) |
||||||
|
|
||||||
|
mPaint!!.color = loadingColor |
||||||
|
canvas.drawArc(loadingRectF!!, topDegree.toFloat(), arc, false, mPaint!!) |
||||||
|
canvas.drawArc(loadingRectF!!, bottomDegree.toFloat(), arc, false, mPaint!!) |
||||||
|
|
||||||
|
topDegree += speedOfDegree |
||||||
|
bottomDegree += speedOfDegree |
||||||
|
if (topDegree > 360) { |
||||||
|
topDegree = topDegree - 360 |
||||||
|
} |
||||||
|
if (bottomDegree > 360) { |
||||||
|
bottomDegree = bottomDegree - 360 |
||||||
|
} |
||||||
|
|
||||||
|
if (changeBigger) { |
||||||
|
if (arc < 160) { |
||||||
|
arc += speedOfArc |
||||||
|
invalidate() |
||||||
|
} |
||||||
|
} else { |
||||||
|
if (arc > speedOfDegree) { |
||||||
|
arc -= 2 * speedOfArc |
||||||
|
invalidate() |
||||||
|
} |
||||||
|
} |
||||||
|
if (arc >= 160 || arc <= 10) { |
||||||
|
changeBigger = !changeBigger |
||||||
|
invalidate() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fun start() { |
||||||
|
startAnimator() |
||||||
|
isStart = true |
||||||
|
invalidate() |
||||||
|
} |
||||||
|
|
||||||
|
fun stop() { |
||||||
|
stopAnimator() |
||||||
|
invalidate() |
||||||
|
} |
||||||
|
|
||||||
|
private fun startAnimator() { |
||||||
|
val scaleXAnimator = ObjectAnimator.ofFloat(this, "scaleX", 0.0f, 1f) |
||||||
|
val scaleYAnimator = ObjectAnimator.ofFloat(this, "scaleY", 0.0f, 1f) |
||||||
|
scaleXAnimator.setDuration(300) |
||||||
|
scaleXAnimator.setInterpolator(LinearInterpolator()) |
||||||
|
scaleYAnimator.setDuration(300) |
||||||
|
scaleYAnimator.setInterpolator(LinearInterpolator()) |
||||||
|
val animatorSet = AnimatorSet() |
||||||
|
animatorSet.playTogether(scaleXAnimator, scaleYAnimator) |
||||||
|
animatorSet.start() |
||||||
|
} |
||||||
|
|
||||||
|
private fun stopAnimator() { |
||||||
|
val scaleXAnimator = ObjectAnimator.ofFloat(this, "scaleX", 1f, 0f) |
||||||
|
val scaleYAnimator = ObjectAnimator.ofFloat(this, "scaleY", 1f, 0f) |
||||||
|
scaleXAnimator.setDuration(300) |
||||||
|
scaleXAnimator.setInterpolator(LinearInterpolator()) |
||||||
|
scaleYAnimator.setDuration(300) |
||||||
|
scaleYAnimator.setInterpolator(LinearInterpolator()) |
||||||
|
val animatorSet = AnimatorSet() |
||||||
|
animatorSet.playTogether(scaleXAnimator, scaleYAnimator) |
||||||
|
animatorSet.addListener(object : Animator.AnimatorListener { |
||||||
|
override fun onAnimationStart(animation: Animator) { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
override fun onAnimationEnd(animation: Animator) { |
||||||
|
isStart = false |
||||||
|
} |
||||||
|
|
||||||
|
override fun onAnimationCancel(animation: Animator) { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
override fun onAnimationRepeat(animation: Animator) { |
||||||
|
|
||||||
|
} |
||||||
|
}) |
||||||
|
animatorSet.start() |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
fun dpToPx(context: Context, dpVal: Float): Int { |
||||||
|
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpVal, context.resources.displayMetrics).toInt() |
||||||
|
} |
||||||
|
|
||||||
|
companion object { |
||||||
|
|
||||||
|
private val DEFAULT_WIDTH = 6 |
||||||
|
private val DEFAULT_SHADOW_POSITION = 2 |
||||||
|
private val DEFAULT_SPEED_OF_DEGREE = 10 |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,56 @@ |
|||||||
|
package io.legado.app.ui.widget.image |
||||||
|
|
||||||
|
import android.annotation.SuppressLint |
||||||
|
import android.content.Context |
||||||
|
import android.graphics.Canvas |
||||||
|
import android.graphics.Path |
||||||
|
import android.util.AttributeSet |
||||||
|
|
||||||
|
|
||||||
|
class CoverImageView : androidx.appcompat.widget.AppCompatImageView { |
||||||
|
internal var width: Float = 0.toFloat() |
||||||
|
internal var height: Float = 0.toFloat() |
||||||
|
|
||||||
|
constructor(context: Context) : super(context) |
||||||
|
|
||||||
|
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) |
||||||
|
|
||||||
|
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) |
||||||
|
|
||||||
|
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { |
||||||
|
super.onLayout(changed, left, top, right, bottom) |
||||||
|
width = getWidth().toFloat() |
||||||
|
height = getHeight().toFloat() |
||||||
|
} |
||||||
|
|
||||||
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { |
||||||
|
val measuredWidth = MeasureSpec.getSize(widthMeasureSpec) |
||||||
|
val measuredHeight = measuredWidth * 7 / 5 |
||||||
|
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.EXACTLY)) |
||||||
|
} |
||||||
|
|
||||||
|
override fun onDraw(canvas: Canvas) { |
||||||
|
if (width >= 10 && height > 10) { |
||||||
|
@SuppressLint("DrawAllocation") |
||||||
|
val path = Path() |
||||||
|
//四个圆角 |
||||||
|
path.moveTo(10f, 0f) |
||||||
|
path.lineTo(width - 10, 0f) |
||||||
|
path.quadTo(width, 0f, width, 10f) |
||||||
|
path.lineTo(width, height - 10) |
||||||
|
path.quadTo(width, height, width - 10, height) |
||||||
|
path.lineTo(10f, height) |
||||||
|
path.quadTo(0f, height, 0f, height - 10) |
||||||
|
path.lineTo(0f, 10f) |
||||||
|
path.quadTo(0f, 0f, 10f, 0f) |
||||||
|
|
||||||
|
canvas.clipPath(path) |
||||||
|
} |
||||||
|
super.onDraw(canvas) |
||||||
|
} |
||||||
|
|
||||||
|
fun setHeight(height: Int) { |
||||||
|
val width = height * 5 / 7 |
||||||
|
minimumWidth = width |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,92 @@ |
|||||||
|
package io.legado.app.ui.widget.image |
||||||
|
|
||||||
|
import android.annotation.SuppressLint |
||||||
|
import android.content.Context |
||||||
|
import android.graphics.Canvas |
||||||
|
import android.graphics.Path |
||||||
|
import android.util.AttributeSet |
||||||
|
import androidx.appcompat.widget.AppCompatImageView |
||||||
|
import io.legado.app.R |
||||||
|
|
||||||
|
class FilletImageView : AppCompatImageView { |
||||||
|
internal var width: Float = 0.toFloat() |
||||||
|
internal var height: Float = 0.toFloat() |
||||||
|
private var leftTopRadius: Int = 0 |
||||||
|
private var rightTopRadius: Int = 0 |
||||||
|
private var rightBottomRadius: Int = 0 |
||||||
|
private var leftBottomRadius: Int = 0 |
||||||
|
|
||||||
|
constructor(context: Context) : super(context) |
||||||
|
|
||||||
|
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { |
||||||
|
init(context, attrs) |
||||||
|
} |
||||||
|
|
||||||
|
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { |
||||||
|
init(context, attrs) |
||||||
|
} |
||||||
|
|
||||||
|
private fun init(context: Context, attrs: AttributeSet) { |
||||||
|
// 读取配置 |
||||||
|
val array = context.obtainStyledAttributes(attrs, R.styleable.FilletImageView) |
||||||
|
val defaultRadius = 5 |
||||||
|
val radius = array.getDimensionPixelOffset(R.styleable.FilletImageView_radius, defaultRadius) |
||||||
|
leftTopRadius = array.getDimensionPixelOffset(R.styleable.FilletImageView_left_top_radius, defaultRadius) |
||||||
|
rightTopRadius = array.getDimensionPixelOffset(R.styleable.FilletImageView_right_top_radius, defaultRadius) |
||||||
|
rightBottomRadius = |
||||||
|
array.getDimensionPixelOffset(R.styleable.FilletImageView_right_bottom_radius, defaultRadius) |
||||||
|
leftBottomRadius = array.getDimensionPixelOffset(R.styleable.FilletImageView_left_bottom_radius, defaultRadius) |
||||||
|
|
||||||
|
//如果四个角的值没有设置,那么就使用通用的radius的值。 |
||||||
|
if (defaultRadius == leftTopRadius) { |
||||||
|
leftTopRadius = radius |
||||||
|
} |
||||||
|
if (defaultRadius == rightTopRadius) { |
||||||
|
rightTopRadius = radius |
||||||
|
} |
||||||
|
if (defaultRadius == rightBottomRadius) { |
||||||
|
rightBottomRadius = radius |
||||||
|
} |
||||||
|
if (defaultRadius == leftBottomRadius) { |
||||||
|
leftBottomRadius = radius |
||||||
|
} |
||||||
|
array.recycle() |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { |
||||||
|
super.onLayout(changed, left, top, right, bottom) |
||||||
|
width = getWidth().toFloat() |
||||||
|
height = getHeight().toFloat() |
||||||
|
} |
||||||
|
|
||||||
|
override fun onDraw(canvas: Canvas) { |
||||||
|
//这里做下判断,只有图片的宽高大于设置的圆角距离的时候才进行裁剪 |
||||||
|
val maxLeft = Math.max(leftTopRadius, leftBottomRadius) |
||||||
|
val maxRight = Math.max(rightTopRadius, rightBottomRadius) |
||||||
|
val minWidth = maxLeft + maxRight |
||||||
|
val maxTop = Math.max(leftTopRadius, rightTopRadius) |
||||||
|
val maxBottom = Math.max(leftBottomRadius, rightBottomRadius) |
||||||
|
val minHeight = maxTop + maxBottom |
||||||
|
if (width >= minWidth && height > minHeight) { |
||||||
|
@SuppressLint("DrawAllocation") val path = Path() |
||||||
|
//四个角:右上,右下,左下,左上 |
||||||
|
path.moveTo(leftTopRadius.toFloat(), 0f) |
||||||
|
path.lineTo(width - rightTopRadius, 0f) |
||||||
|
path.quadTo(width, 0f, width, rightTopRadius.toFloat()) |
||||||
|
|
||||||
|
path.lineTo(width, height - rightBottomRadius) |
||||||
|
path.quadTo(width, height, width - rightBottomRadius, height) |
||||||
|
|
||||||
|
path.lineTo(leftBottomRadius.toFloat(), height) |
||||||
|
path.quadTo(0f, height, 0f, height - leftBottomRadius) |
||||||
|
|
||||||
|
path.lineTo(0f, leftTopRadius.toFloat()) |
||||||
|
path.quadTo(0f, 0f, leftTopRadius.toFloat(), 0f) |
||||||
|
|
||||||
|
canvas.clipPath(path) |
||||||
|
} |
||||||
|
super.onDraw(canvas) |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,175 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto" |
||||||
|
xmlns:tools="http://schemas.android.com/tools" |
||||||
|
android:id="@+id/cv_content" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:clickable="true" |
||||||
|
android:focusable="true" |
||||||
|
android:foreground="?android:attr/selectableItemBackground" |
||||||
|
tools:ignore="UnusedAttribute"> |
||||||
|
|
||||||
|
<io.legado.app.ui.widget.image.CoverImageView |
||||||
|
android:id="@+id/iv_cover" |
||||||
|
android:layout_width="60dp" |
||||||
|
android:layout_height="80dp" |
||||||
|
android:layout_margin="8dp" |
||||||
|
android:contentDescription="@string/img_cover" |
||||||
|
android:scaleType="centerCrop" |
||||||
|
android:src="@drawable/img_cover_default" |
||||||
|
android:transitionName="img_cover" |
||||||
|
app:layout_constraintBottom_toBottomOf="parent" |
||||||
|
app:layout_constraintLeft_toLeftOf="parent" |
||||||
|
app:layout_constraintTop_toTopOf="parent" |
||||||
|
tools:ignore="UnusedAttribute" /> |
||||||
|
|
||||||
|
<FrameLayout |
||||||
|
android:id="@+id/fl_has_new" |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
app:layout_constraintRight_toRightOf="parent" |
||||||
|
app:layout_constraintTop_toTopOf="@id/tv_name"> |
||||||
|
|
||||||
|
<io.legado.app.ui.widget.BadgeView |
||||||
|
android:id="@+id/bv_unread" |
||||||
|
android:layout_width="wrap_content" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:layout_gravity="right" |
||||||
|
android:layout_margin="5dp" |
||||||
|
android:includeFontPadding="false" |
||||||
|
tools:ignore="RtlHardcoded" /> |
||||||
|
|
||||||
|
<com.victor.loading.rotate.RotateLoading |
||||||
|
android:id="@+id/rl_loading" |
||||||
|
android:layout_width="26dp" |
||||||
|
android:layout_height="26dp" |
||||||
|
android:layout_gravity="right" |
||||||
|
android:visibility="invisible" |
||||||
|
app:loading_color="@color/colorAccent" |
||||||
|
app:loading_width="2dp" |
||||||
|
tools:ignore="RtlHardcoded" /> |
||||||
|
</FrameLayout> |
||||||
|
|
||||||
|
<TextView |
||||||
|
android:id="@+id/tv_name" |
||||||
|
android:layout_width="0dp" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:layout_marginLeft="8dp" |
||||||
|
android:layout_marginTop="8dp" |
||||||
|
android:includeFontPadding="false" |
||||||
|
android:paddingLeft="4dp" |
||||||
|
android:singleLine="true" |
||||||
|
android:text="@string/book_name" |
||||||
|
android:textColor="@color/tv_text_default" |
||||||
|
android:textSize="16sp" |
||||||
|
app:layout_constraintBottom_toTopOf="@+id/tv_author" |
||||||
|
app:layout_constraintLeft_toRightOf="@+id/iv_cover" |
||||||
|
app:layout_constraintRight_toLeftOf="@id/fl_has_new" |
||||||
|
app:layout_constraintTop_toTopOf="parent" |
||||||
|
tools:ignore="RtlHardcoded,RtlSymmetry" /> |
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView |
||||||
|
android:id="@+id/iv_author" |
||||||
|
android:layout_width="@dimen/desc_icon_size" |
||||||
|
android:layout_height="@dimen/desc_icon_size" |
||||||
|
android:layout_marginLeft="8dp" |
||||||
|
android:contentDescription="@string/author" |
||||||
|
android:paddingStart="2dp" |
||||||
|
android:paddingEnd="2dp" |
||||||
|
android:src="@drawable/ic_author" |
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/tv_author" |
||||||
|
app:layout_constraintLeft_toRightOf="@+id/iv_cover" |
||||||
|
app:layout_constraintTop_toTopOf="@+id/tv_author" |
||||||
|
app:tint="@color/tv_text_secondary" |
||||||
|
tools:ignore="RtlHardcoded,RtlSymmetry" /> |
||||||
|
|
||||||
|
<TextView |
||||||
|
android:id="@+id/tv_author" |
||||||
|
android:layout_width="0dp" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:includeFontPadding="false" |
||||||
|
android:maxLines="1" |
||||||
|
android:paddingEnd="6dp" |
||||||
|
android:text="@string/author" |
||||||
|
android:textColor="@color/tv_text_secondary" |
||||||
|
android:textSize="13sp" |
||||||
|
app:layout_constraintBottom_toTopOf="@+id/tv_read" |
||||||
|
app:layout_constraintLeft_toRightOf="@+id/iv_author" |
||||||
|
app:layout_constraintRight_toLeftOf="@id/fl_has_new" |
||||||
|
app:layout_constraintTop_toBottomOf="@+id/tv_name" |
||||||
|
tools:ignore="RtlSymmetry" /> |
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView |
||||||
|
android:id="@+id/iv_read" |
||||||
|
android:layout_width="@dimen/desc_icon_size" |
||||||
|
android:layout_height="@dimen/desc_icon_size" |
||||||
|
android:layout_marginLeft="8dp" |
||||||
|
android:contentDescription="@string/read_dur_progress" |
||||||
|
android:paddingStart="2dp" |
||||||
|
android:paddingEnd="2dp" |
||||||
|
android:src="@drawable/ic_history" |
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/tv_read" |
||||||
|
app:layout_constraintLeft_toRightOf="@+id/iv_cover" |
||||||
|
app:layout_constraintTop_toTopOf="@+id/tv_read" |
||||||
|
app:tint="@color/tv_text_secondary" |
||||||
|
tools:ignore="RtlHardcoded,RtlSymmetry" /> |
||||||
|
|
||||||
|
<TextView |
||||||
|
android:id="@+id/tv_read" |
||||||
|
android:layout_width="0dp" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:includeFontPadding="false" |
||||||
|
android:singleLine="true" |
||||||
|
android:text="@string/read_dur_progress" |
||||||
|
android:textColor="@color/tv_text_secondary" |
||||||
|
android:textSize="13sp" |
||||||
|
app:layout_constraintBottom_toTopOf="@id/tv_last" |
||||||
|
app:layout_constraintLeft_toRightOf="@+id/iv_read" |
||||||
|
app:layout_constraintRight_toLeftOf="@id/fl_has_new" |
||||||
|
app:layout_constraintTop_toBottomOf="@+id/tv_author" /> |
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView |
||||||
|
android:id="@+id/iv_last" |
||||||
|
android:layout_width="@dimen/desc_icon_size" |
||||||
|
android:layout_height="@dimen/desc_icon_size" |
||||||
|
android:layout_marginLeft="8dp" |
||||||
|
android:contentDescription="@string/book_search_last" |
||||||
|
android:paddingStart="2dp" |
||||||
|
android:paddingEnd="2dp" |
||||||
|
android:src="@drawable/ic_book_last" |
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/tv_last" |
||||||
|
app:layout_constraintLeft_toRightOf="@+id/iv_cover" |
||||||
|
app:layout_constraintTop_toTopOf="@+id/tv_last" |
||||||
|
app:tint="@color/tv_text_secondary" |
||||||
|
tools:ignore="RtlHardcoded,RtlSymmetry" /> |
||||||
|
|
||||||
|
<TextView |
||||||
|
android:id="@+id/tv_last" |
||||||
|
android:layout_width="0dp" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:layout_marginBottom="8dp" |
||||||
|
android:singleLine="true" |
||||||
|
android:text="@string/book_search_last" |
||||||
|
android:textColor="@color/tv_text_secondary" |
||||||
|
android:textSize="13sp" |
||||||
|
app:layout_constraintBottom_toBottomOf="parent" |
||||||
|
app:layout_constraintLeft_toRightOf="@+id/iv_last" |
||||||
|
app:layout_constraintRight_toLeftOf="@id/fl_has_new" |
||||||
|
app:layout_constraintTop_toBottomOf="@+id/tv_read" /> |
||||||
|
|
||||||
|
<View |
||||||
|
android:id="@+id/vw_select" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="0dp" |
||||||
|
android:visibility="gone" |
||||||
|
app:layout_constraintTop_toTopOf="parent" |
||||||
|
app:layout_constraintBottom_toBottomOf="parent" /> |
||||||
|
|
||||||
|
<View |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="0.5dp" |
||||||
|
android:background="@color/btn_bg_press" |
||||||
|
app:layout_constraintBottom_toBottomOf="parent" /> |
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout> |
Loading…
Reference in new issue