写点什么

做个酷炫的“锤子” 开关效果,kotlinwindows 桌面开发

用户头像
Android架构
关注
发布于: 刚刚


绘制代码 onDraw 中,按照形状分类拆分不同的方法。需要注意的是,先绘制状态指示器,它在最下层


@Override


protected void onDraw(Canvas canvas) {


super.onDraw(canvas);


setLayerType(LAYER_TYPE_SOFTWARE, null);


//绘制状态指示器


drawFlag(canvas);


//绘制背景区域


drawBackgroundArea(canvas);


//绘制开关指示器


drawIndicator(canvas);


}


2.1 UI 绘制



2.1.1 开关背景


内阴影的绘制思路我在《微质感的层级选择器,隔壁产品都馋哭了》提过,感兴趣的请翻看我的往期文章。


https://zhuanlan.zhihu.com/p/185805279


代码如下:


void drawBackgroundArea(Canvas canvas) {


//绘制边框及内阴影


canvas.save();


backgroundAreaPaint.setStyle(Paint.Style.STROKE);


int strokeW = indicatorR / 2;


backgroundAreaPaint.setStrokeWidth(strokeW);


backgroundAreaPaint.setColor(Color.parseColor("#66bcbcbc"));


backgroundAreaShadowSize = backgroundAreaH / 4;


backgroundAreaShadowDistance = backgroundAreaH / 12;


backgroundAreaPaint.setShadowLayer(backgroundAreaShadowSize + shadowOffset, 0, backgroundAreaShadowDistance, Color.GRAY);


RectF strokeRectF = new RectF(-strokeW + (width - backgroundAreaW) / 2, -strokeW + (height - backgroundAreaH) / 2, strokeW + (width - backgroundAreaW) / 2 + backgroundAreaW, strokeW + (height - backgroundAreaH) / 2 + backgroundAreaH);


Path strokePath = new Path();


strokePath.addRoundRect(strokeRectF, (backgroundAreaH + strokeW) / 2, (backgroundAreaH + strokeW) / 2, Path.Direction.CW);


RectF rectF = new RectF((width - backgroundAreaW) / 2, (height - backgroundAreaH) / 2, (width - backgroundAreaW) / 2 + backgroundAreaW, (height - backgroundAreaH) / 2 + backgroundAreaH);


Path path = new Path();


path.addRoundRect(rectF, backgroundAreaH / 2, backgroundAreaH / 2, Path.Direction.CW);


canvas.clipPath(path);


canvas.drawPath(strokePath, backgroundAreaPaint);


backgroundAreaPaint.setStrokeWidth(2);


backgroundAreaPaint.clearShadowLayer();


canvas.drawPath(path, backgroundAreaPaint);


canvas.restore();


}

2.1.2 开关指示器


其中包括了凸显立体感的内阴影以及外阴影的绘制,见代码:


void drawIndicator(Canvas canvas) {


//绘制外阴影


indicatorPaint.setColor(indicatorColor);


indicatorPaint.setStyle(Paint.Style.FILL);


indicatorShadowSize = indicatorR / 3;


indicatorShadowDistance = indicatorShadowSize / 2;


indicatorPaint.setShadowLayer(indicatorShadowSize - shadowOffset, 0, indicatorShadowDistance, Color.parseColor("#ffc1c1c1"));


canvas.drawCircle(indicatorX + indicatorXOffset, (height - backgroundAreaH) / 2 + indicatorR, indicatorR, indicatorPaint);


//绘制内阴影


canvas.save();


indicatorPaint.setColor(Color.parseColor("#66bcbcbc"));


int strokeW = indicatorR / 2;


indicatorPaint.setStrokeWidth(strokeW);


indicatorPaint.setStyle(Paint.Style.STROKE);


indicatorPaint.setShadowLayer(indicatorR / 3, -indicatorR / 6, -indicatorR / 6, Color.parseColor("#fff1f1f1"));


Path strokePath = new Path();


strokePath.addCircle(indicatorX + indicatorXOffset, (height - backgroundAreaH) / 2 + indicatorR, indicatorR + strokeW / 2, Path.Direction.CW);


Path path = new Path();


path.addCircle(indicatorX + indicatorXOffset, (height - backgroundAreaH) / 2 + indicatorR, indicatorR, Path.Direction.CW);


canvas.clipPath(path);


canvas.drawPath(strokePath, indicatorPaint);


indicatorPaint.setStrokeWidth(2);


indicatorPaint.clearShadowLayer();


canvas.drawPath(path, indicatorPaint);


canvas.restore();


}

2.1.3 状态指示器


这一步中也包含内阴影的绘制。额外注意连续调用两次 canvas.save 方法,通过 clipPath 裁剪出背景区域形状的画布。


void drawFlag(Canvas canvas) {


//首先裁剪出背景圆角矩形画布


canvas.save();


RectF rectF = new RectF((width - backgroundAreaW) / 2, (height - backgroundAreaH) / 2, (width - backgroundAreaW) / 2 + backgroundAreaW, (height - backgroundAreaH) / 2 + backgroundAreaH);


Path bgAreaPath = new Path();


bgAreaPath.addRoundRect(rectF, backgroundAreaH / 2, backgroundAreaH / 2, Path.Direction.CW);


canvas.clipPath(bgAreaPath);


//绘制 on flag


flagPaint.setStyle(Paint.Style.FILL);


flagPaint.setColor(onColor);


flagPaint.clearShadowLayer();


canvas.drawCircle(indicatorX + indicatorXOffset - backgroundAreaW * 3 / 5, height / 2, indicatorR / 4, flagPaint);


//内阴影


flagPaint.setStyle(Paint.Style.STROKE);


int onStrokeW = indicatorR / 4;


flagPaint.setStrokeWidth(onStrokeW);


flagPaint.setShadowLayer(onStrokeW, -onStrokeW, onStrokeW, onColor);


Path onPath = new Path();


onPath.addCircle(indicatorX + indicatorXOffset - backgroundAreaW * 3 / 5, height / 2, indicatorR / 4 + onStrokeW / 2, Path.Direction.CW);


canvas.save();


canvas.clipPath(onPath);


canvas.drawPath(onPath, flagPaint);


flagPaint.clearShadowLayer();


canvas.restore();


//绘制 off flag


flagPaint.setStyle(Paint.Style.FILL);


flagPaint.setColor(offColor);


canvas.drawCircle(indicatorX + indicatorXOffset + backgroundAreaW * 3 / 5, height / 2, indicatorR / 4, flagPaint);


//内阴影


flagPaint.setStyle(Paint.Style.STROKE);


int offStrokeW = indicatorR / 4;


flagPaint.setStrokeWidth(offStrokeW);


flagPaint.setShadowLayer(offStrokeW, -offStrokeW, offStrokeW, offColor);


Path offPa


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


th = new Path();


offPath.addCircle(indicatorX + indicatorXOffset + backgroundAreaW * 3 / 5, height / 2, indicatorR / 4 + offStrokeW / 2, Path.Direction.CW);


canvas.save();


canvas.clipPath(offPath);


canvas.drawPath(offPath, flagPaint);


canvas.restore();


canvas.restore();


}


2.2 交互实现

2.2.1 边界判断

当用户滑动超过边界时,强制重新赋值


indicatorXOffset = (int) (event.getX() - downX);


//边界判断


if (indicatorX + indicatorXOffset <= (width - backgroundAreaW) / 2 + indicatorR) {


indicatorXOffset = (width - backgroundAreaW) / 2 + indicatorR - indicatorX;


} else if (indicatorX + indicatorXOffset >= width - (width - backgroundAreaW) / 2 - indicatorR) {


indicatorXOffset = width - (width - backgroundAreaW) / 2 - indicatorR - indicatorX;


}

2.2.2 区分滑动和点按

注意一个细节,当用户的滑动距离非常小时,算作点按,此时控件的开关状态要改变;滑动距离超过一定阈值时,算滑动操作,此时控件的开关状态不一定改变


if (Math.abs(indicatorXOffset) <= 20) {


//todo:点按操作


}else{


//todo:滑动操作


}

2.2.3 状态变化

定义一个字段 isChecked 表示开关状态,根据开关指示器位置判断状态是否应该改变

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
做个酷炫的“锤子” 开关效果,kotlinwindows桌面开发