Android自定义竖直拖动条(VerticalSeekBar)
如图:
1、自定义属性 res->values下创建attrs.xml文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <!-- 仪表盘自定义属性 --> <declare-styleable name= "MySeekBar" > <!--背景颜色--> <attr name= "bgColor" format= "color" /> <!--进度颜色--> <attr name= "progressColor" format= "color" /> <!--当前进度--> <attr name= "progress" format= "float" /> <!--总进度--> <attr name= "maxProgress" format= "float" /> <!--是否显示小太阳--> <attr name= "showSun" format= "boolean" /> <!--是否缩放小太阳--> <attr name= "zoomSun" format= "boolean" /> <!--小太阳颜色--> <attr name= "sunColor" format= "color" /> <!--小太阳半径--> <attr name= "circleRadius" format= "dimension" /> <!--矩形圆角--> <attr name= "radiusXY" format= "dimension" /> <attr name= "showText" format= "boolean" /> <attr name= "setTextColor" format= "color" /> <attr name= "setTextHeight" format= "integer|dimension" /> <attr name= "setTextSize" format= "dimension" /> </declare-styleable> |
2、自定义View
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 | package com.example.customseekbar; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.RectF; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; import com.example.customseekbar.utils.MyColorUtils; public class MySeekBar extends View { //背景颜色 private int bgColor; //进度颜色 private int progressColor; //当前进度 private float progress = 10 ; //总进度 private float maxProgress = 100 ; //当前UI高度与view高度的比例 private double progressRate = 0 ; //记录按压时手指相对于组件view的高度 private float downY; //手指移动的距离,视为亮度调整 private float moveDistance; private Paint mPaint; //画笔 //是否画亮度路标 private boolean showSun = true ; //太阳颜色 private int sunColor; //小太阳半径 private float circleRadius = 15 ; //矩形圆角 private float radiusXY = 40 ; //设置是否画亮度文字 private boolean showText = true ; //文字位置 private int textHeight = - 1 ; //字体大写 private float textSize = 15 ; //字体颜色 private int textColor = Color.BLACK; //显示内容 private String textContent = "" ; //是否缩放小太阳 private boolean isSunZoom = true ; //亮度图标margin private int margin = 10 ; public MySeekBar(Context context) { this (context, null ); } public MySeekBar(Context context, @Nullable AttributeSet attrs) { this (context, attrs, 0 ); } public MySeekBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super (context, attrs, defStyleAttr); initAttrs(context, attrs); } private void initAttrs(Context context, AttributeSet attrs) { textSize = sp2px(context,textSize); TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MySeekBar); bgColor = typedArray.getColor(R.styleable.MySeekBar_bgColor, ContextCompat.getColor(getContext(),R.color.bg_color)); progressColor = typedArray.getColor(R.styleable.MySeekBar_progressColor,ContextCompat.getColor(getContext(),R.color.progress_color)); progress = typedArray.getFloat(R.styleable.MySeekBar_progress, 10 ); maxProgress = typedArray.getFloat(R.styleable.MySeekBar_maxProgress, 100 ); showSun = typedArray.getBoolean(R.styleable.MySeekBar_showSun,showSun); isSunZoom = typedArray.getBoolean(R.styleable.MySeekBar_zoomSun,isSunZoom); sunColor = typedArray.getColor(R.styleable.MySeekBar_sunColor,ContextCompat.getColor(getContext(),R.color.sun_color)); circleRadius = typedArray.getDimension(R.styleable.MySeekBar_circleRadius, circleRadius); radiusXY = typedArray.getDimension(R.styleable.MySeekBar_radiusXY, radiusXY); showText = typedArray.getBoolean(R.styleable.MySeekBar_showText,showText); textColor = typedArray.getColor(R.styleable.MySeekBar_setTextColor,textColor); textHeight = typedArray.getInt(R.styleable.MySeekBar_setTextHeight,textHeight); textSize = typedArray.getDimension(R.styleable.MySeekBar_setTextSize,textSize); typedArray.recycle(); initPaint(); } private void initPaint() { //获取当前百分比率 progressRate = getProgressRate(); //背景画笔 mPaint = new Paint(); mPaint.setAntiAlias( true ); //抗锯齿 mPaint.setDither( true ); //设置防抖动 mPaint.setColor(bgColor); mPaint.setStyle(Paint.Style.FILL); mPaint.setStrokeWidth( 0 ); mPaint.setStrokeCap(Paint.Cap.ROUND); mPaint.setTextSize(textSize); } public int getProgress(){ return ( int )progress; } public void setProgress( int mProgress){ progress = mProgress; progressRate = getProgressRate(); invalidate(); } /** * 计算亮度比例 */ private double getProgressRate(){ return ( double ) progress / maxProgress; } @Override protected void onDraw(Canvas canvas) { super .onDraw(canvas); //保存 int layerId = canvas.saveLayer( 0 , 0 , canvas.getWidth(), canvas.getHeight(), null , Canvas.ALL_SAVE_FLAG); onDrawBackground(canvas); //画背景 onDrawProgress(canvas); //画进度 onDrawText(canvas); //画文字 onDrawSunCircle(canvas); //画小太郎 //恢复到特定的保存点 canvas.restoreToCount(layerId); } /** * 画圆弧背景 * @param canvas */ private void onDrawBackground(Canvas canvas){ mPaint.setStyle(Paint.Style.FILL); mPaint.setColor(bgColor); int with = getWidth(); int height = getHeight(); // int colors [] = new int[2]; // //colors[0] = Color.parseColor("#FFB660"); // //colors[1] = Color.parseColor("#FFA757"); // colors[0] = setColorTempToColor("6500K"); // colors[1] = setColorTempToColor("2700K"); // //线性变色 // float heightkkk = (canvas.getHeight()-(int)(canvas.getHeight() * progressRate)); // LinearGradient linearGradient = new LinearGradient((canvas.getWidth()/2),heightkkk,(canvas.getWidth()/2),0,colors,null, Shader.TileMode.CLAMP); // //new float[]{},中的数据表示相对位置,将150,50,150,300,划分10个单位,.3,.6,.9表示它的绝对位置。300到400,将直接画出rgb(0,232,210) // mPaint.setShader(linearGradient); RectF rectF = new RectF( 0 , 0 ,with,height); canvas.drawRoundRect(rectF,radiusXY,radiusXY,mPaint); } /** * 画亮度背景-方形-随手势上下滑动而变化用来显示亮度大小 * @param canvas */ private void onDrawProgress(Canvas canvas){ mPaint.setStyle(Paint.Style.FILL); mPaint.setXfermode( new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP)); int with = getWidth(); int height = getHeight(); // int colors [] = new int[2]; // //colors[0] = Color.parseColor("#FFB660"); // //colors[1] = Color.parseColor("#FFA757"); // colors[0] = setColorTempToColor("6500K"); // colors[1] = setColorTempToColor("2700K"); // //线性变色 // float heightkkk = (canvas.getHeight()-(int)(canvas.getHeight() * progressRate)); // LinearGradient linearGradient = new LinearGradient((canvas.getWidth()/2),heightkkk,(canvas.getWidth()/2),height,colors,null, Shader.TileMode.CLAMP); // //new float[]{},中的数据表示相对位置,将150,50,150,300,划分10个单位,.3,.6,.9表示它的绝对位置。300到400,将直接画出rgb(0,232,210) // mPaint.setShader(linearGradient); mPaint.setColor(progressColor); Log.i( "打印比率:" , "progressRate = " +progressRate); float progressHeight = (canvas.getHeight()-( int )(canvas.getHeight() * progressRate)); canvas.drawRect( 0 ,progressHeight,with,height,mPaint); mPaint.setXfermode( null ); } /** * 画文字-展示当前大小 * @param canvas */ private void onDrawText(Canvas canvas){ if (showText) { //如果开启了则开始绘制 mPaint.setStyle(Paint.Style.FILL); textContent = "" + ( int ) (progressRate * 100 ); mPaint.setColor(textColor); canvas.drawText(textContent, (canvas.getWidth() / 2 - mPaint.measureText(textContent) / 2 ), textHeight >= 0 ? textHeight : getHeight() / 6 , mPaint); } } /** * 画亮度图标-太阳圆心 */ private void onDrawSunCircle(Canvas canvas){ if (showSun){ //如果开启了则开始绘制 mPaint.setStyle(Paint.Style.FILL); mPaint.setStrokeWidth( 4 ); mPaint.setColor(sunColor); if (isSunZoom){ //是否缩放太阳圆点 float circleMaxRadius = ( float ) (Math.sqrt(canvas.getWidth()) * 1.5 ); float circleMinRadius = ( float ) (Math.sqrt(canvas.getWidth()) * 1 ); //当前圆半径 circleRadius = ( float ) progressRate * (circleMaxRadius - circleMinRadius) + circleMinRadius; canvas.drawCircle(canvas.getWidth()/ 2 ,( float ) (canvas.getHeight() * 0.85 - margin), circleRadius,mPaint); onDrawSunRays(canvas,canvas.getWidth()/ 2 ,( float ) (canvas.getHeight() * 0.85 - margin)); } else { canvas.drawCircle(canvas.getWidth()/ 2 ,( float ) (canvas.getHeight() * 0.85 ), circleRadius,mPaint); onDrawSunRays(canvas,canvas.getWidth()/ 2 ,( float ) (canvas.getHeight() * 0.85 )); } } } /** * 画亮度图标-太阳光芒 */ private void onDrawSunRays(Canvas canvas, float cx, float cy){ mPaint.setStrokeCap(Paint.Cap.ROUND); // 定义线段断电形状为圆头 //绘制时刻度 canvas.translate(cx,cy); for ( int i = 0 ; i < 10 ; i++) { if (isSunZoom){ //是否缩放 canvas.drawLine(circleRadius, circleRadius, ( float )(circleRadius + 5 * progressRate),( float )( circleRadius + 5 * progressRate), mPaint); } else { canvas.drawLine(circleRadius, circleRadius, ( float )(circleRadius + 5 ),( float )( circleRadius + 5 ), mPaint); } canvas.rotate( 36 ); } } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: downY = event.getY(); break ; case MotionEvent.ACTION_MOVE: moveDistance = downY - event.getY(); //计算手指移动后亮度UI占比大小 calculateLoudRate(); downY = event.getY(); if (listener != null ) { listener.onProgressChange(( int )(progressRate* 100 )); } break ; case MotionEvent.ACTION_UP: break ; } invalidate(); return true ; } /** * 计算手指移动后亮度UI占比大小,视其为亮度大小 */ private void calculateLoudRate(){ progressRate = ( getHeight() * progressRate + moveDistance) / getHeight(); if (progressRate >= 1 ){ progressRate = 1 ; } if (progressRate <= 0 ){ progressRate = 0 ; } } @Override protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec) { super .onMeasure(widthMeasureSpec, heightMeasureSpec); margin = MeasureSpec.getSize(widthMeasureSpec)/ 10 ; } @Override protected void onLayout( boolean changed, int left, int top, int right, int bottom) { super .onLayout(changed, left, top, right, bottom); } //附加 @Override protected void onAttachedToWindow() { super .onAttachedToWindow(); } //分离,拆卸 @Override protected void onDetachedFromWindow() { super .onDetachedFromWindow(); } /** * 根据手机的分辨率从 dp 的单位 转成为 px(像素) */ public static int dp2px(Context context, float dpValue) { final float scale = context.getResources().getDisplayMetrics().density; return ( int ) (dpValue * scale + 0 .5f); } /** * 将sp值转换为px值,保证文字大小不变 */ public int sp2px(Context context, float spValue) { final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; return ( int ) (spValue * fontScale + 0 .5f); } //进度发生变化的回调接口 interface OnProgressChangedListener { void onProgressChange( int progress); } public void setOnProgressChangedListener(OnProgressChangedListener listener) { this .listener = listener; } //进度移动监听 private OnProgressChangedListener listener = null ; private int setColorTempToColor(String color){ String zhi = color.replace( "K" , "" ); Log.i( "打印色温值:" , "" +zhi); int rgb [] = new int [ 3 ]; rgb = MyColorUtils.getRgbFromTemperature(Double.valueOf(zhi), false ); Log.i( "打印色温值转颜色:" , "R=${rgb[0]} ,G=${rgb[1]} ,B=${rgb[2]}" ); int c = Color.argb( 255 ,rgb[ 0 ], rgb[ 1 ], rgb[ 2 ]); Log.i( "打印选择的值3" , "c=${c}" ); //返回颜色 return c; } } |
3、布局文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | <?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:layout_width= "match_parent" android:layout_height= "match_parent" android:background= "#00FF00" tools:context= ".MySeekBarActivity" > <com.example.customseekbar.MySeekBar android:id= "@+id/my_seekbar" android:layout_width= "100dp" android:layout_height= "300dp" app:progress= "60" app:maxProgress= "100" app:bgColor= "@color/bg_color" app:progressColor= "@color/progress_color" app:layout_constraintStart_toStartOf= "parent" app:layout_constraintEnd_toEndOf= "parent" app:layout_constraintTop_toTopOf= "parent" app:layout_constraintBottom_toBottomOf= "parent" ></com.example.customseekbar.MySeekBar> <TextView android:id= "@+id/tv_value" android:layout_width= "wrap_content" android:layout_height= "wrap_content" android:layout_marginTop= "20dp" android:text= "" android:textSize= "16sp" app:layout_constraintStart_toStartOf= "parent" app:layout_constraintEnd_toEndOf= "parent" app:layout_constraintTop_toBottomOf= "@id/my_seekbar" ></TextView> </androidx.constraintlayout.widget.ConstraintLayout> |
4、activity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public class MySeekBarActivity extends AppCompatActivity { private MySeekBar mySeekBar; private TextView tvValue; @Override protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_my_seek_bar); tvValue = findViewById(R.id.tv_value); mySeekBar = findViewById(R.id.my_seekbar); mySeekBar.setOnProgressChangedListener( new MySeekBar.OnProgressChangedListener() { @Override public void onProgressChange( int progress) { tvValue.setText( "" +progress); //tvValue.setText(""+(2700+(6500-2700)/100*progress)); } }); //设置默认 mySeekBar.setProgress( 90 ); tvValue.setText( "" +mySeekBar.getProgress()); } } |
color
1 2 3 | <color name= "bg_color" >#FFF4ED</color> <color name= "progress_color" >#FFE5C5</color> <color name= "sun_color" >#F6B160</color> |
完成
分类:
自定义view
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
2016-08-07 XML的Pull解析
2016-08-07 HttpURLConnection请求网络数据