Android 圆弧形 SeekBar

news2025/1/4 19:33:46

效果预览

package com.gcssloop.widget;

import android.annotation.SuppressLint;

import android.content.Context;

import android.content.res.TypedArray;

import android.graphics.Canvas;

import android.graphics.Color;

import android.graphics.Matrix;

import android.graphics.Paint;

import android.graphics.Path;

import android.graphics.PathMeasure;

import android.graphics.RectF;

import android.graphics.Region;

import android.graphics.SweepGradient;

import android.os.Bundle;

import android.os.Parcelable;

import android.util.AttributeSet;

import android.util.TypedValue;

import android.view.GestureDetector;

import android.view.MotionEvent;

import android.view.View;

import com.gcssloop.arcseekbar.R;

import static android.view.MotionEvent.ACTION_CANCEL;

import static android.view.MotionEvent.ACTION_DOWN;

import static android.view.MotionEvent.ACTION_MOVE;

import static android.view.MotionEvent.ACTION_UP;

public class ArcSeekBar extends View {

private static final int DEFAULT_EDGE_LENGTH = 260; // 默认宽高

private static final float CIRCLE_ANGLE = 360; // 圆周角

private static final int DEFAULT_ARC_WIDTH = 40; // 默认宽度 dp

private static final float DEFAULT_OPEN_ANGLE = 120; // 开口角度

private static final float DEFAULT_ROTATE_ANGLE = 90; // 旋转角度

private static final int DEFAULT_BORDER_WIDTH = 0; // 默认描边宽度

private static final int DEFAULT_BORDER_COLOR = 0xffffffff; // 默认描边颜色

private static final int DEFAULT_THUMB_COLOR = 0xffffffff; // 拖动按钮颜色

private static final int DEFAULT_THUMB_WIDTH = 2; // 拖动按钮描边宽度 dp

private static final int DEFAULT_THUMB_RADIUS = 15; // 拖动按钮半径 dp

private static final int DEFAULT_THUMB_SHADOW_RADIUS = 0; // 拖动按钮阴影半径 dp

private static final int DEFAULT_THUMB_SHADOW_COLOR = 0xFF000000; // 拖动按钮阴影颜色

private static final int DEFAULT_SHADOW_RADIUS = 0; // 默认阴影半径 dp

private static final int THUMB_MODE_STROKE = 0; // 拖动按钮模式 - 描边

private static final int THUMB_MODE_FILL = 1; // 拖动按钮模式 - 填充

private static final int THUMB_MODE_FILL_STROKE = 2; // 拖动按钮模式 - 填充+描边

private static final int DEFAULT_MAX_VALUE = 100; // 默认最大数值

private static final int DEFAULT_MIN_VALUE = 0; // 默认最小数值

private static final String KEY_PROGRESS_PRESENT = "PRESENT"; // 用于存储和获取当前百分比

// 可配置数据

private int[] mArcColors; // Seek 颜色

private float mArcWidth; // Seek 宽度

private float mOpenAngle; // 开口的角度大小 0 - 360

private float mRotateAngle; // 旋转角度

private int mBorderWidth; // 描边宽度

private int mBorderColor; // 描边颜色

private int mThumbColor; // 拖动按钮颜色

private float mThumbWidth; // 拖动按钮宽度

private float mThumbRadius; // 拖动按钮半径

private float mThumbShadowRadius;// 拖动按钮阴影半径

private int mThumbShadowColor;// 拖动按钮阴影颜色

private int mThumbMode; // 拖动按钮模式

private int mShadowRadius; // 阴影半径

private int mMaxValue; // 最大数值

private int mMinValue; // 最小数值

private float mCenterX; // 圆弧 SeekBar 中心点 X

private float mCenterY; // 圆弧 SeekBar 中心点 Y

private float mThumbX; // 拖动按钮 中心点 X

private float mThumbY; // 拖动按钮 中心点 Y

private Path mSeekPath;

private Path mBorderPath;

private Paint mArcPaint;

private Paint mThumbPaint;

private Paint mBorderPaint;

private Paint mShadowPaint;

private float[] mTempPos;

private float[] mTempTan;

private PathMeasure mSeekPathMeasure;

private float mProgressPresent = 0; // 当前进度百分比

private boolean mCanDrag = false; // 是否允许拖动

private boolean mAllowTouchSkip = false; // 是否允许越过边界

private GestureDetector mDetector;

private Matrix mInvertMatrix; // 逆向 Matrix, 用于计算触摸坐标和绘制坐标的转换

private Region mArcRegion; // ArcPath的实际区域大小,用于判定单击事件

public ArcSeekBar(Context context) {

this(context, null);

}

public ArcSeekBar(Context context, AttributeSet attrs) {

this(context, attrs, 0);

}

public ArcSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

setSaveEnabled(true);

setLayerType(LAYER_TYPE_SOFTWARE, null);

initAttrs(context, attrs);

initData();

initPaint();

}

// 初始化各种属性

private void initAttrs(Context context, AttributeSet attrs) {

TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ArcSeekBar);

mArcColors = getArcColors(context, ta);

mArcWidth = ta.getDimensionPixelSize(R.styleable.ArcSeekBar_arc_width, dp2px(DEFAULT_ARC_WIDTH));

mOpenAngle = ta.getFloat(R.styleable.ArcSeekBar_arc_open_angle, DEFAULT_OPEN_ANGLE);

mRotateAngle = ta.getFloat(R.styleable.ArcSeekBar_arc_rotate_angle, DEFAULT_ROTATE_ANGLE);

mMaxValue = ta.getInt(R.styleable.ArcSeekBar_arc_max, DEFAULT_MAX_VALUE);

mMinValue = ta.getInt(R.styleable.ArcSeekBar_arc_min, DEFAULT_MIN_VALUE);

// 如果用户设置的最大值和最小值不合理,则直接按照默认进行处理

if (mMaxValue <= mMinValue) {

mMaxValue = DEFAULT_MAX_VALUE;

mMinValue = DEFAULT_MIN_VALUE;

}

int progress = ta.getInt(R.styleable.ArcSeekBar_arc_progress, mMinValue);

setProgress(progress);

mBorderWidth = ta.getDimensionPixelSize(R.styleable.ArcSeekBar_arc_border_width, dp2px(DEFAULT_BORDER_WIDTH));

mBorderColor = ta.getColor(R.styleable.ArcSeekBar_arc_border_color, DEFAULT_BORDER_COLOR);

mThumbColor = ta.getColor(R.styleable.ArcSeekBar_arc_thumb_color, DEFAULT_THUMB_COLOR);

mThumbRadius = ta.getDimensionPixelSize(R.styleable.ArcSeekBar_arc_thumb_radius, dp2px(DEFAULT_THUMB_RADIUS));

mThumbShadowRadius = ta.getDimensionPixelSize(R.styleable.ArcSeekBar_arc_thumb_shadow_radius, dp2px(DEFAULT_THUMB_SHADOW_RADIUS));

mThumbShadowColor = ta.getColor(R.styleable.ArcSeekBar_arc_thumb_shadow_color, DEFAULT_THUMB_SHADOW_COLOR);

mThumbWidth = ta.getDimensionPixelSize(R.styleable.ArcSeekBar_arc_thumb_width, dp2px(DEFAULT_THUMB_WIDTH));

mThumbMode = ta.getInt(R.styleable.ArcSeekBar_arc_thumb_mode, THUMB_MODE_STROKE);

mShadowRadius = ta.getDimensionPixelSize(R.styleable.ArcSeekBar_arc_shadow_radius, dp2px(DEFAULT_SHADOW_RADIUS));

ta.recycle();

}

// 获取 Arc 颜色数组

private int[] getArcColors(Context context, TypedArray ta) {

int[] ret;

int resId = ta.getResourceId(R.styleable.ArcSeekBar_arc_colors, 0);

if (0 == resId) {

resId = R.array.arc_colors_default;

}

ret = getColorsByArrayResId(context, resId);

return ret;

}

// 根据 resId 获取颜色数组

private int[] getColorsByArrayResId(Context context, int resId) {

int[] ret;

TypedArray colorArray = context.getResources().obtainTypedArray(resId);

ret = new int[colorArray.length()];

for (int i = 0; i < colorArray.length(); i++) {

ret[i] = colorArray.getColor(i, 0);

}

return ret;

}

// 初始化数据

private void initData() {

mSeekPath = new Path();

mBorderPath = new Path();

mSeekPathMeasure = new PathMeasure();

mTempPos = new float[2];

mTempTan = new float[2];

mDetector = new GestureDetector(getContext(), new OnClickListener());

mInvertMatrix = new Matrix();

mArcRegion = new Region();

}

// 初始化画笔

private void initPaint() {

initArcPaint();

initThumbPaint();

initBorderPaint();

initShadowPaint();

}

// 初始化圆弧画笔

private void initArcPaint() {

mArcPaint = new Paint();

mArcPaint.setAntiAlias(true);

mArcPaint.setStrokeWidth(mArcWidth);

mArcPaint.setStyle(Paint.Style.STROKE);

mArcPaint.setStrokeCap(Paint.Cap.ROUND);

}

// 初始化拖动按钮画笔

private void initThumbPaint() {

mThumbPaint = new Paint();

mThumbPaint.setAntiAlias(true);

mThumbPaint.setColor(mThumbColor);

mThumbPaint.setStrokeWidth(mThumbWidth);

mThumbPaint.setStrokeCap(Paint.Cap.ROUND);

if (mThumbMode == THUMB_MODE_FILL) {

mThumbPaint.setStyle(Paint.Style.FILL_AND_STROKE);

} else if (mThumbMode == THUMB_MODE_FILL_STROKE) {

mThumbPaint.setStyle(Paint.Style.FILL_AND_STROKE);

} else {

mThumbPaint.setStyle(Paint.Style.STROKE);

}

mThumbPaint.setTextSize(56);

}

// 初始化拖动按钮画笔

private void initBorderPaint() {

mBorderPaint = new Paint();

mBorderPaint.setAntiAlias(true);

mBorderPaint.setColor(mBorderColor);

mBorderPaint.setStrokeWidth(mBorderWidth);

mBorderPaint.setStyle(Paint.Style.STROKE);

}

// 初始化阴影画笔

private void initShadowPaint() {

mShadowPaint = new Paint();

mShadowPaint.setAntiAlias(true);

mShadowPaint.setStrokeWidth(mBorderWidth);

mShadowPaint.setStyle(Paint.Style.FILL_AND_STROKE);

}

@Override

protected Parcelable onSaveInstanceState() {

Bundle bundle = new Bundle();

bundle.putParcelable("superState", super.onSaveInstanceState());

bundle.putFloat(KEY_PROGRESS_PRESENT, mProgressPresent);

return bundle;

}

@Override

protected void onRestoreInstanceState(Parcelable state) {

if (state instanceof Bundle) {

Bundle bundle = (Bundle) state;

this.mProgressPresent = bundle.getFloat(KEY_PROGRESS_PRESENT);

state = bundle.getParcelable("superState");

}

if (null != mOnProgressChangeListener) {

mOnProgressChangeListener.onProgressChanged(this, getProgress(), false);

}

super.onRestoreInstanceState(state);

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

int ws = MeasureSpec.getSize(widthMeasureSpec); //取出宽度的确切数值

int wm = MeasureSpec.getMode(widthMeasureSpec); //取出宽度的测量模式

int hs = MeasureSpec.getSize(heightMeasureSpec); //取出高度的确切数值

int hm = MeasureSpec.getMode(heightMeasureSpec); //取出高度的测量模

if (wm == MeasureSpec.UNSPECIFIED) {

wm = MeasureSpec.EXACTLY;

ws = dp2px(DEFAULT_EDGE_LENGTH);

} else if (wm == MeasureSpec.AT_MOST) {

wm = MeasureSpec.EXACTLY;

ws = Math.min(dp2px(DEFAULT_EDGE_LENGTH), ws);

}

if (hm == MeasureSpec.UNSPECIFIED) {

hm = MeasureSpec.EXACTLY;

hs = dp2px(DEFAULT_EDGE_LENGTH);

} else if (hm == MeasureSpec.AT_MOST) {

hm = MeasureSpec.EXACTLY;

hs = Math.min(dp2px(DEFAULT_EDGE_LENGTH), hs);

}

setMeasuredDimension(MeasureSpec.makeMeasureSpec(ws, wm), MeasureSpec.makeMeasureSpec(hs, hm));

}

@Override

protected void onSizeChanged(int w, int h, int oldw, int oldh) {

super.onSizeChanged(w, h, oldw, oldh);

// 计算在当前大小下,内容应该显示的大小和起始位置

int safeW = w - getPaddingLeft() - getPaddingRight();

int safeH = h - getPaddingTop() - getPaddingBottom();

float edgeLength, startX, startY;

float fix = mArcWidth / 2 + mBorderWidth + mShadowRadius * 2; // 修正距离,画笔宽度的修正

if (safeW < safeH) {

// 宽度小于高度,以宽度为准

edgeLength = safeW - fix;

startX = getPaddingLeft();

startY = (safeH - safeW) / 2.0f + getPaddingTop();

} else {

// 宽度大于高度,以高度为准

edgeLength = safeH - fix;

startX = (safeW - safeH) / 2.0f + getPaddingLeft();

startY = getPaddingTop();

}

// 得到显示区域和中心的

RectF content = new RectF(startX + fix, startY + fix, startX + edgeLength, startY + edgeLength);

mCenterX = content.centerX();

mCenterY = content.centerY();

// 得到路径

mSeekPath.reset();

mSeekPath.addArc(content, mOpenAngle / 2, CIRCLE_ANGLE - mOpenAngle);

mSeekPathMeasure.setPath(mSeekPath, false);

computeThumbPos(mProgressPresent);

resetShaderColor();

mInvertMatrix.reset();

mInvertMatrix.preRotate(-mRotateAngle, mCenterX, mCenterY);

mArcPaint.getFillPath(mSeekPath, mBorderPath);

mBorderPath.close();

mArcRegion.setPath(mBorderPath, new Region(0, 0, w, h));

}

// 重置 shader 颜色

private void resetShaderColor() {

// 计算渐变数组

float startPos = (mOpenAngle / 2) / CIRCLE_ANGLE;

float stopPos = (CIRCLE_ANGLE - (mOpenAngle / 2)) / CIRCLE_ANGLE;

int len = mArcColors.length - 1;

float distance = (stopPos - startPos) / len;

float pos[] = new float[mArcColors.length];

for (int i = 0; i < mArcColors.length; i++) {

pos[i] = startPos + (distance * i);

}

SweepGradient gradient = new SweepGradient(mCenterX, mCenterY, mArcColors, pos);

mArcPaint.setShader(gradient);

}

// 具体绘制

@Override

protected void onDraw(Canvas canvas) {

canvas.save();

canvas.rotate(mRotateAngle, mCenterX, mCenterY);

mShadowPaint.setShadowLayer(mShadowRadius * 2, 0, 0, getColor());

canvas.drawPath(mBorderPath, mShadowPaint);

canvas.drawPath(mSeekPath, mArcPaint);

if (mBorderWidth > 0) {

canvas.drawPath(mBorderPath, mBorderPaint);

}

if (mThumbShadowRadius > 0) {

mThumbPaint.setShadowLayer(mThumbShadowRadius, 0, 0, mThumbShadowColor);

canvas.drawCircle(mThumbX, mThumbY, mThumbRadius, mThumbPaint);

mThumbPaint.clearShadowLayer();

}

canvas.drawCircle(mThumbX, mThumbY, mThumbRadius, mThumbPaint);

canvas.restore();

}

private boolean moved = false;

private int lastProgress = -1;

@SuppressLint("ClickableViewAccessibility")

@Override

public boolean onTouchEvent(MotionEvent event) {

super.onTouchEvent(event);

int action = event.getActionMasked();

switch (action) {

case ACTION_DOWN:

moved = false;

judgeCanDrag(event);

if (null != mOnProgressChangeListener) {

mOnProgressChangeListener.onStartTrackingTouch(this);

}

break;

case ACTION_MOVE:

if (!mCanDrag) {

break;

}

float tempProgressPresent = getCurrentProgress(event.getX(), event.getY());

if (!mAllowTouchSkip) {

// 不允许突变

if (Math.abs(tempProgressPresent - mProgressPresent) > 0.5f) {

break;

}

}

// 允许突变 或者非突变

mProgressPresent = tempProgressPresent;

computeThumbPos(mProgressPresent);

// 事件回调

if (null != mOnProgressChangeListener && getProgress() != lastProgress) {

mOnProgressChangeListener.onProgressChanged(this, getProgress(), true);

lastProgress = getProgress();

}

moved = true;

break;

case ACTION_UP:

case ACTION_CANCEL:

if (null != mOnProgressChangeListener && moved) {

mOnProgressChangeListener.onStopTrackingTouch(this);

}

break;

}

mDetector.onTouchEvent(event);

invalidate();

return true;

}

// 判断是否允许拖动

private void judgeCanDrag(MotionEvent event) {

float[] pos = {event.getX(), event.getY()};

mInvertMatrix.mapPoints(pos);

if (getDistance(pos[0], pos[1]) <= mThumbRadius * 1.5) {

mCanDrag = true;

} else {

mCanDrag = false;

}

}

private class OnClickListener extends GestureDetector.SimpleOnGestureListener {

@Override

public boolean onSingleTapUp(MotionEvent e) {

// 判断是否点击在了进度区域

if (!isInArcProgress(e.getX(), e.getY())) return false;

// 点击允许突变

mProgressPresent = getCurrentProgress(e.getX(), e.getY());

computeThumbPos(mProgressPresent);

// 事件回调

if (null != mOnProgressChangeListener) {

mOnProgressChangeListener.onProgressChanged(ArcSeekBar.this, getProgress(), true);

mOnProgressChangeListener.onStopTrackingTouch(ArcSeekBar.this);

}

return true;

}

}

// 判断该点是否在进度条上面

private boolean isInArcProgress(float px, float py) {

float[] pos = {px, py};

mInvertMatrix.mapPoints(pos);

return mArcRegion.contains((int) pos[0], (int) pos[1]);

}

// 获取当前进度理论进度数值

private float getCurrentProgress(float px, float py) {

float diffAngle = getDiffAngle(px, py);

float progress = diffAngle / (CIRCLE_ANGLE - mOpenAngle);

if (progress < 0) progress = 0;

if (progress > 1) progress = 1;

return progress;

}

// 获得当前点击位置所成角度与开始角度之间的数值差

private float getDiffAngle(float px, float py) {

float angle = getAngle(px, py);

float diffAngle;

diffAngle = angle - mRotateAngle;

if (diffAngle < 0) {

diffAngle = (diffAngle + CIRCLE_ANGLE) % CIRCLE_ANGLE;

}

diffAngle = diffAngle - mOpenAngle / 2;

return diffAngle;

}

// 计算指定位置与内容区域中心点的夹角

private float getAngle(float px, float py) {

float angle = (float) ((Math.atan2(py - mCenterY, px - mCenterX)) * 180 / 3.14f);

if (angle < 0) {

angle += 360;

}

return angle;

}

// 计算指定位置与上次位置的距离

private float getDistance(float px, float py) {

return (float) Math.sqrt((px - mThumbX) * (px - mThumbX) + (py - mThumbY) * (py - mThumbY));

}

private int dp2px(int dp) {

return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getContext().getResources().getDisplayMetrics());

}

// 计算拖动块应该显示的位置

private void computeThumbPos(float present) {

if (present < 0) present = 0;

if (present > 1) present = 1;

if (null == mSeekPathMeasure) return;

float distance = mSeekPathMeasure.getLength() * present;

mSeekPathMeasure.getPosTan(distance, mTempPos, mTempTan);

mThumbX = mTempPos[0];

mThumbY = mTempPos[1];

}

/**

* 获取当前进度的具体颜色

*

* @return 当前进度在渐变中的颜色

*/

public int getColor() {

return getColor(mProgressPresent);

}

/**

* 获取某个百分比位置的颜色

*

* @param radio 取值[0,1]

* @return 最终颜色

*/

private int getColor(float radio) {

float diatance = 1.0f / (mArcColors.length - 1);

int startColor;

int endColor;

if (radio >= 1) {

return mArcColors[mArcColors.length - 1];

}

for (int i = 0; i < mArcColors.length; i++) {

if (radio <= i * diatance) {

if (i == 0) {

return mArcColors[0];

}

startColor = mArcColors[i - 1];

endColor = mArcColors[i];

float areaRadio = getAreaRadio(radio, diatance * (i - 1), diatance * i);

return getColorFrom(startColor, endColor, areaRadio);

}

}

return -1;

}

/**

* 计算当前比例在子区间的比例

*

* @param radio 总比例

* @param startPosition 子区间开始位置

* @param endPosition 子区间结束位置

* @return 自区间比例[0, 1]

*/

private float getAreaRadio(float radio, float startPosition, float endPosition) {

return (radio - startPosition) / (endPosition - startPosition);

}

/**

* 取两个颜色间的渐变区间 中的某一点的颜色

*

* @param startColor 开始的颜色

* @param endColor 结束的颜色

* @param radio 比例 [0, 1]

* @return 选中点的颜色

*/

private int getColorFrom(int startColor, int endColor, float radio) {

int redStart = Color.red(startColor);

int blueStart = Color.blue(startColor);

int greenStart = Color.green(startColor);

int redEnd = Color.red(endColor);

int blueEnd = Color.blue(endColor);

int greenEnd = Color.green(endColor);

int red = (int) (redStart + ((redEnd - redStart) * radio + 0.5));

int greed = (int) (greenStart + ((greenEnd - greenStart) * radio + 0.5));

int blue = (int) (blueStart + ((blueEnd - blueStart) * radio + 0.5));

return Color.argb(255, red, greed, blue);

}

/**

* 设置进度

*

* @param progress 进度值

*/

public void setProgress(int progress) {

System.out.println("setProgress = " + progress);

if (progress > mMaxValue) progress = mMaxValue;

if (progress < mMinValue) progress = mMinValue;

mProgressPresent = (progress - mMinValue) * 1.0f / (mMaxValue - mMinValue);

System.out.println("setProgress present = " + mProgressPresent);

if (null != mOnProgressChangeListener) {

mOnProgressChangeListener.onProgressChanged(this, progress, false);

}

computeThumbPos(mProgressPresent);

postInvalidate();

}

/**

* 获取当前进度数值

*

* @return 当前进度数值

*/

public int getProgress() {

return (int) (mProgressPresent * (mMaxValue - mMinValue)) + mMinValue;

}

/**

* 设置颜色

*

* @param colors 颜色

*/

public void setArcColors(int[] colors) {

mArcColors = colors;

resetShaderColor();

postInvalidate();

}

/**

* 设置最大数值

* @param max 最大数值

*/

public void setMaxValue(int max) {

mMaxValue = max;

}

/**

* 设置最小数值

* @param min 最小数值

*/

public void setMinValue(int min) {

mMinValue = min;

}

/**

* 设置颜色

*

* @param colorArrayRes 颜色资源 R.array.arc_color

*/

public void setArcColors(int colorArrayRes) {

setArcColors(getColorsByArrayResId(getContext(), colorArrayRes));

}

private OnProgressChangeListener mOnProgressChangeListener;

public void setOnProgressChangeListener(OnProgressChangeListener onProgressChangeListener) {

mOnProgressChangeListener = onProgressChangeListener;

}

public interface OnProgressChangeListener {

/**

* 进度发生变化

*

* @param seekBar 拖动条

* @param progress 当前进度数值

* @param isUser 是否是用户操作, true 表示用户拖动, false 表示通过代码设置

*/

void onProgressChanged(ArcSeekBar seekBar, int progress, boolean isUser);

/**

* 用户开始拖动

*

* @param seekBar 拖动条

*/

void onStartTrackingTouch(ArcSeekBar seekBar);

/**

* 用户结束拖动

*

* @param seekBar 拖动条

*/

void onStopTrackingTouch(ArcSeekBar seekBar);

}

}

attrs.xml代码:

<?xml version="1.0" encoding="utf-8"?>

<resources>

<declare-styleable name="ArcSeekBar">

<attr name="arc_width" format="dimension|reference" />

<attr name="arc_open_angle" format="float" />

<attr name="arc_rotate_angle" format="float" />

<attr name="arc_colors" format="reference" />

<attr name="arc_border_width" format="dimension|reference" />

<attr name="arc_border_color" format="color|reference" />

<attr name="arc_max" format="integer|reference" />

<attr name="arc_min" format="integer|reference" />

<attr name="arc_progress" format="integer|reference" />

<attr name="arc_thumb_width" format="dimension|reference" />

<attr name="arc_thumb_color" format="color|reference" />

<attr name="arc_thumb_radius" format="dimension|reference" />

<attr name="arc_thumb_shadow_radius" format="dimension|reference" />

<attr name="arc_thumb_shadow_color" format="color|reference" />

<attr name="arc_thumb_mode" format="integer|dimension">

<enum name="STROKE" value="0" />

<enum name="FILL" value="1" />

<enum name="FILL_STROKE" value="2" />

</attr>

<attr name="arc_shadow_radius" format="dimension|reference" />

</declare-styleable>

</resources>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/386113.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ViT(Vision Transformer) TNT(Transformer in Transformer)

ViT(Vision Transformer) ViT的结构 ViT将输入图片分为多个patch&#xff08;16x16&#xff09;&#xff0c;再将每个patch投影为固定长度的向量送入Transformer&#xff0c;后续encoder的操作和原始Transformer中完全相同。但是因为对图片分类&#xff0c;因此在输入序列中加…

论文复现-3

模型构建中的运算 数据集是CONLL03 这个数据集共有4种实体类型&#xff0c;所以&#xff0c;在做实体描述的embedding时&#xff0c;得到的语义表示的Tensor大小为 &#xff1a; 4*max_len, 具体指的是&#xff1a; type_input_ids: torch.LongTensor None, type_attention_m…

2023年中职组网络安全竞赛——web服务渗透测试解析

web服务渗透测试(100分) 题目如下: 解析如下: PS:任务环境可以私信博主,求个三连吧! 通过本地PC中的渗透测试平台KALI2020对靶机进行WEB渗透,找到页面内的文件上传漏洞并且尝试进行上传攻击,将文件上传成功后的页面回显字符串作为FLAG提交(如:点击超链接查看上传文…

Python自动化测试之requests库【发送json数据类型 】(六)

目录&#xff1a;导读 数据类型 application/json&#xff1a; application/x-www-form-urlencoded text/xml requests发送json 写在最后 我们都知道post请求中的body有4种数据类型&#xff0c;今天我们来写一篇关于requests请求json这种数据类型。 数据类型 我们都知道…

使用JSON.stringify的第三个参数,美化序列化后的值

事情是这样的&#xff0c;我在使用tiptap富文本编辑器&#xff0c;展示JSON代码&#xff0c;效果图肯定是这样的假设我有一个javascript对象如下const data {a: test }想要实现上面的效果&#xff0c;肯定需要使用JSON.stringifyconst editorData JSON.stringify(data)editor…

Linux中安装JDK8.跟学韩顺平

Linux中安装JDK8第16章Linux之JavaEE定制篇搭建JavaEE环境16.1 概述16.2安装JDK16.2.1安装步骤16.2.2测试是否安装成功第16章Linux之JavaEE定制篇搭建JavaEE环境 16.1 概述 如果需要在Linux下进行JavaEE的开发&#xff0c;我们需要安装如下软件 资料下载地址&#xff1a;百度…

【算法】BloomFilter概念和原理以及业务中的应用场景

思考&#xff1a;海量数据下去重&#xff0c;如果是非数值类型的话如何判断&#xff1f;1.什么是布隆过滤器 1970年由布隆提出的一种空间效率很高的概率型数据结构&#xff0c;它可以用于检索一个元素是否在一个集合中。 由只存0或1的位数组和多个hash算法, 进行判断数据 【一…

2023-03-03 mysql列存储-cpu占用100%-追踪思路

摘要: 最近在处理mysql列存储时, 发现在执行explain时, cpu占用达到了100%. 本文分析定位该问题的思路过程 现象: mysqld进程占用100%使用kill processlist终止会话, 无响应查看show processings; 发现一直在运行mysql> show processlist; +----+-----------------+-----…

图片服务器

文章目录一、项目简介二、功能及场景三、业务设计四、数据库设计准备图片表准备实体类五、API设计常用功能封装文件上传文件上传获取图片列表接口获取图片内容删除图片接口六、项目优化七、测试自动化测试测试用例一、项目简介 图片服务器&#xff1a;解决项目中插入图片的问题…

Java 运算符与类型转化

Java 运算符与类型转化 1 算术运算符 Java中的算术运算符主要有&#xff08;加&#xff09;、-&#xff08;减&#xff09;、*&#xff08;乘&#xff09;、/&#xff08;除&#xff09;、%&#xff08;求余&#xff09;&#xff0c;它们都是二元运算符。 2 自增和自减运算…

Day906.grant语句 -MySQL实战

grant语句 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于grant语句的内容。 在 MySQL 里面&#xff0c;grant 语句是用来给用户赋权的。 不知道有没有见过一些操作文档里面提到&#xff0c;grant 之后要马上跟着执行一个 flush privileges 命令&#xff0c;才能使…

搞懂它,带你学会高效配置交换机!

想了解更多IT学习资料&#xff0c;可关注公众号“IT运维大本营” 不同网段的用户想要进行三层通信&#xff0c;必须借助于路由表项&#xff0c;而VLANIF接口只能生成直连路由&#xff0c;实现不同网段间通过同一台设备互通&#xff0c;对于不同网段间跨设备…

【Linux】虚拟机设置ISO镜像、配置CentOS 7、设置快照

目录 一、设置ISO镜像 1.设置或编辑 2.配置光驱&#xff08;DVD&#xff09; 3.虚拟机快捷键设置 4.启动虚拟机 二、配置CentOS 7 三、设置快照 四、​​​​​​​虚拟网卡不显示怎么办&#xff1f; &#x1f49f; 创作不易&#xff0c;不妨点赞&#x1f49a;评论❤️收…

安装打印机驱动程序的操作步骤,详细方法介绍

安装打印机驱动程序是使用打印机的前提条件&#xff0c;因此学会正确的安装方法是非常重要的。下面是安装打印机驱动程序的详细步骤分析&#xff0c;为你全面的讲解安装和出现故障如何处理等问题。 一.安装打印机的准备工作 在安装打印机驱动程序之前&#xff0c;需要先准备好…

【Java集合框架】篇六:Collections工具类

Collections 是一个操作 Set、List 和 Map 等集合的工具类。 1。 常用方法 Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作&#xff0c;还提供了对集合对象设置不可变、对集合对象实现同步控制等方法&#xff08;均为static方法&#xff09;&…

软件测试6

一 css书写位置&#xff0c;引入方式 1.内嵌式&#xff1a;css写在style标签中&#xff0c;放在title标签的后面&#xff0c;因为html代码和css代码混在同一个文件中&#xff0c;所以叫内嵌式。 2.外链式&#xff1a;css代码和html代码分离&#xff0c;使用link标签设置href属…

UE 蓝图上帝视角

UE 蓝图上帝视角 UE5蓝图实现相机上帝视角控制&#xff0c;包括绕点旋转&#xff0c;水平平移&#xff0c;远近缩放 实现效果 绕点旋转 水平平移 实现功能 左键鼠标平移&#xff08;相对于场景水平面平移&#xff09;右键绕点旋转中间键视角前后移动&#xff08;可以理解成…

linux网络管理、测试网络连通性

一、网络管理 在rhel7上&#xff0c;同时支持network.service和NetworkMananger.service&#xff08;简称NM&#xff09;。在rhel8上&#xff0c;已经废弃network.service&#xff0c;因此只能通过NM进行网络配置&#xff0c;包括静态ip和动态ip。在rhel8上&#xff0c;必须开启…

安卓短信自动填充踩坑

安卓短信自动填充踩坑 前言 最近弄了个短信自动填充功能&#xff0c;一开始觉得很简单&#xff0c;不就是动态注册个广播接收器去监听短信消息不就可以了吗&#xff1f;结果没这么简单&#xff0c;问题就出在机型的适配上。小米的短信权限、荣耀的短信监听都是坑&#xff0c;…

LabVIEW绘制带有两个不同标尺的波形图/图表

LabVIEW绘制带有两个不同标尺的波形图/图表拥有多组不同标尺的数据&#xff0c;想要在LabVIEW中显示这些数据。能否在同一张波形图/图表中使用多个不同的标尺绘制这些数据&#xff1f;通过在同一波形图上使用多个轴&#xff0c;可以使用不同的标尺绘制数据。请按照以下步骤操作…