package com.ht.gate; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.DrawFilter; import android.graphics.LinearGradient; import android.graphics.Paint; import android.graphics.PaintFlagsDrawFilter; import android.graphics.Path; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Shader; import android.graphics.Typeface; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.util.TypedValue; import android.view.MotionEvent; import android.view.View; import androidx.annotation.Nullable; import androidx.core.content.res.ResourcesCompat; public class UnlockView extends View { private Paint borderPaint; private Paint linePaint; private Paint textPaint; private Shader lineShader; private Path borderPath; private float borderWidth; private Bitmap bmUnlock; private Bitmap bmUnlocked; private float distance = 0; private float padding = 0; private boolean down; private float lastX; private Rect src, src1; private RectF dst, dst1; private DrawFilter drawFilter; private Typeface source_han_sans_sc_normal; private Typeface source_han_sans_sc_light; private int colorLight = 0xFFFFFFFF; private int colorDark = 0xFF222639; private int colorUnlockedLight = 0x11C1C3CD; private int colorUnlockedDark = 0x1E222639; private boolean dark; private boolean preUnlock = false; private String hint; private UnlockListener unlockListener; private boolean unlocked; public interface UnlockListener { void onUnlock(); } public void setUnlockListener(UnlockListener unlockListener) { this.unlockListener = unlockListener; } public UnlockView(Context context) { this(context, null); } public UnlockView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public UnlockView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public UnlockView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); TypedArray t = getContext().obtainStyledAttributes(attrs, R.styleable.UnlockView); hint = t.getString(R.styleable.UnlockView_hint); if (TextUtils.isEmpty(hint)) { hint = "滑动解锁"; } dark = t.getBoolean(R.styleable.UnlockView_darkMode, false); t.recycle(); source_han_sans_sc_normal = ResourcesCompat.getFont(getContext(), R.font.source_han_sans_sc_normal); source_han_sans_sc_light = ResourcesCompat.getFont(getContext(), R.font.source_han_sans_sc_light); borderPaint = new Paint(); borderPaint.setAntiAlias(true); borderWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, getContext().getResources().getDisplayMetrics()); borderPaint.setStrokeWidth(borderWidth); borderPaint.setStyle(Paint.Style.STROKE); borderPaint.setFlags(Paint.ANTI_ALIAS_FLAG); borderPath = new Path(); padding = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 7, getContext().getResources().getDisplayMetrics()); bmUnlock = BitmapFactory.decodeResource(getResources(), dark ? R.mipmap.icon_unlock_dark : R.mipmap.icon_unlock); bmUnlocked = BitmapFactory.decodeResource(getResources(), dark ? R.mipmap.icon_unlocked_dark : R.mipmap.icon_unlocked); src = new Rect(0, 0, bmUnlock.getWidth(), bmUnlock.getHeight()); dst = new RectF(); src1 = new Rect(0, 0, bmUnlocked.getWidth(), bmUnlocked.getHeight()); dst1 = new RectF(); drawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); linePaint = new Paint(); linePaint.setAntiAlias(true); linePaint.setStrokeCap(Paint.Cap.ROUND); textPaint = new Paint(); textPaint.setAntiAlias(true); textPaint.setTextSize(sp2px(25)); textPaint.setStyle(Paint.Style.FILL); textPaint.setTypeface(source_han_sans_sc_light); } public void setUnlocked(boolean unlocked) { this.unlocked = unlocked; postInvalidate(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @SuppressLint("DrawAllocation") @Override protected void onDraw(Canvas canvas) { canvas.setDrawFilter(drawFilter); float width = getWidth(); float height = getHeight(); float d = (height - borderWidth); float r = d / 2; if (unlocked) { borderPaint.setColor(dark ? colorUnlockedDark : colorUnlockedLight); borderPaint.setStyle(Paint.Style.FILL); textPaint.setColor(dark ? colorDark : colorLight); canvas.drawRoundRect(0, 0, width, height, height / 2, height / 2, borderPaint); float textWidth = textPaint.measureText("已解锁"); float baseLineY = Math.abs(textPaint.ascent() + textPaint.descent()) / 2 + getHeight() / 2f; canvas.drawText("已解锁", (getWidth() + dp2px(32)) / 2f - textWidth / 2, baseLineY, textPaint); dst1.left = (width - textPaint.measureText("已解锁") - dp2px(32)) / 2; dst1.top = (height - dp2px(26)) / 2; dst1.right = dst1.left + dp2px(21); dst1.bottom = dst1.top + dp2px(26); canvas.drawBitmap(bmUnlocked, src1, dst1, null); return; } else { borderPaint.setStyle(Paint.Style.STROKE); if (dark) { borderPaint.setColor(colorDark); textPaint.setColor(colorDark); } else { borderPaint.setColor(colorLight); textPaint.setColor(colorLight); } } borderPath.reset(); borderPath.moveTo(r + borderWidth / 2, borderWidth / 2); borderPath.lineTo(r + borderWidth / 2, borderWidth / 2); borderPath.lineTo(width - r, borderWidth / 2); borderPath.arcTo(width - d - borderWidth / 2, borderWidth / 2, width - borderWidth / 2, height - borderWidth / 2, -90, 180, true); borderPath.lineTo(r + borderWidth / 2, height - borderWidth / 2); borderPath.arcTo(borderWidth / 2, borderWidth / 2, d + borderWidth / 2, height - borderWidth / 2, 90, 180, true); canvas.drawPath(borderPath, borderPaint); float size = height - padding * 2; int dd = (int) Math.max(0, Math.min(distance, getWidth() - padding * 2 - size)); linePaint.setStrokeWidth(height - padding * 2); lineShader = dark ? new LinearGradient(0, 0, width, 0, new int[]{0x00222639, 0x1F222639, 0x78222639, 0xD4222639}, new float[]{0, 0.19f, 0.42f, 1}, Shader.TileMode.CLAMP) : new LinearGradient(0, 0, width, 0, new int[]{Color.argb(0, 255, 255, 255), Color.argb(30, 255, 255, 255), Color.argb(117, 255, 255, 255), Color.argb(255, 255, 255, 255)}, new float[]{0, 0.19f, 0.42f, 1}, Shader.TileMode.CLAMP); linePaint.setShader(lineShader); canvas.drawLine(r, height / 2, r + dd, height / 2, linePaint); drawCenterText(canvas, textPaint, hint); dst.left = (int) padding + dd; dst.top = (int) padding; dst.right = (int) (padding + size + dd); dst.bottom = (int) (padding + size); canvas.drawBitmap(bmUnlock, src, dst, null); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (event.getX() < getHeight() && event.getY() < getHeight()) { down = true; lastX = event.getX(); } break; case MotionEvent.ACTION_UP: distance = 0; down = false; preUnlock = false; postInvalidate(); break; case MotionEvent.ACTION_MOVE: if (down) { distance += event.getX() - lastX; lastX = event.getX(); if (distance >= getWidth() - padding * 2 - (getHeight() - padding * 2) && !preUnlock) { preUnlock = true; if (unlockListener != null) { unlockListener.onUnlock(); } } postInvalidate(); } break; } return true; } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); if (bmUnlock != null && !bmUnlock.isRecycled()) { bmUnlock.recycle(); bmUnlock = null; } } private void drawCenterText(Canvas canvas, Paint paint, String text) { // 文本宽 float textWidth = paint.measureText(text); // 文本baseline在y轴方向的位置 float baseLineY = Math.abs(paint.ascent() + paint.descent()) / 2 + getHeight() / 2f; canvas.drawText(text, getWidth() / 2f - textWidth / 2, baseLineY, paint); } public int sp2px(float sp) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, getContext().getResources().getDisplayMetrics()); } public int dp2px(float dp) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getContext().getResources().getDisplayMetrics()); } }