Android 天气APP(十三)仿微信弹窗(右上角加号点击弹窗效果)、自定义背景图片、UI优化调整

97 阅读16分钟

22dp

23dp

24dp

25dp

26dp

27dp

28dp

29dp

30dp

31dp

32dp

33dp

34dp

35dp

36dp

37dp

38dp

39dp

40dp

41dp

42dp

43dp

44dp

45dp

46dp

47dp

48dp

49dp

50dp

51dp

52dp

53dp

54dp

55dp

56dp

57dp

58dp

59dp

60dp

61dp

62dp

63dp

64dp

65dp

66dp

67dp

68dp

69dp

70dp

71dp

72dp

73dp

74dp

75dp

76dp

77dp

78dp

79dp

80dp

81dp

82dp

83dp

84dp

85dp

86dp

87dp

88dp

89dp

90dp

91dp

92dp

93dp

94dp

95dp

96dp

97dp

98dp

99dp

100dp

101dp

102dp

103dp

104dp

105dp

106dp

107dp

108dp

109dp

110dp

111dp

112dp

113dp

114dp

115dp

116dp

117dp

118dp

119dp

120dp

121dp

122dp

123dp

124dp

125dp

126dp

127dp

128dp

129dp

130dp

131dp

132dp

133dp

134dp

135dp

136dp

137dp

138dp

139dp

140dp

141dp

142dp

143dp

144dp

145dp

146dp

147dp

148dp

149dp

150dp

151dp

152dp

153dp

154dp

155dp

156dp

157dp

158dp

159dp

160dp

161dp

162dp

163dp

164dp

165dp

166dp

167dp

168dp

169dp

170dp

171dp

172dp

173dp

174dp

175dp

176dp

177dp

178dp

179dp

180dp

181dp

182dp

183dp

184dp

185dp

186dp

187dp

188dp

189dp

190dp

191dp

192dp

193dp

194dp

195dp

196dp

197dp

198dp

199dp

200dp

201dp

202dp

203dp

204dp

205dp

206dp

207dp

208dp

209dp

210dp

211dp

212dp

213dp

214dp

215dp

216dp

217dp

218dp

219dp

220dp

221dp

222dp

223dp

224dp

225dp

226dp

227dp

228dp

229dp

230dp

231dp

232dp

233dp

234dp

235dp

236dp

237dp

238dp

239dp

240dp

241dp

242dp

243dp

244dp

245dp

246dp

247dp

248dp

249dp

250dp

251dp

252dp

253dp

254dp

255dp

256dp

257dp

258dp

259dp

260dp

261dp

262dp

263dp

264dp

265dp

266dp

267dp

268dp

269dp

270dp

271dp

272dp

273dp

274dp

275dp

276dp

277dp

278dp

279dp

280dp

281dp

282dp

283dp

284dp

285dp

286dp

287dp

288dp

289dp

290dp

291dp

292dp

293dp

294dp

295dp

296dp

297dp

298dp

299dp

300dp

301dp

302dp

303dp

304dp

305dp

306dp

307dp

308dp

309dp

310dp

311dp

312dp

313dp

314dp

315dp

316dp

317dp

318dp

319dp

320dp

321dp

322dp

323dp

324dp

325dp

326dp

327dp

328dp

329dp

330dp

331dp

332dp

333dp

334dp

335dp

336dp

337dp

338dp

339dp

340dp

341dp

342dp

343dp

344dp

345dp

346dp

347dp

348dp

349dp

350dp

351dp

352dp

353dp

354dp

355dp

356dp

357dp

358dp

359dp

360dp

365dp

370dp

400dp

410dp

422dp

472dp

500dp

600dp

640dp

720dp

6sp

7sp

8sp

9sp

10sp

11sp

12sp

13sp

14sp

15sp

16sp

17sp

18sp

19sp

20sp

21sp

22sp

23sp

24sp

25sp

26sp

27sp

28sp

29sp

30sp

31sp

32sp

33sp

34sp

35sp

36sp

37sp

38sp

40sp

42sp

48sp

弹窗页面的布局如下:

<LinearLayout

xmlns:android="schemas.android.com/apk/res/and…"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:background="@color/transparent"

android:orientation="vertical">

<LinearLayout

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_marginEnd="@dimen/dp_8"

android:background="@mipmap/icon_add_bg_9"

android:orientation="vertical"

android:paddingBottom="@dimen/dp_20"

android:paddingTop="@dimen/dp_20">

<TextView

android:id="@+id/tv_change_city"

android:gravity="center"

android:layout_width="@dimen/dp_140"

android:layout_height="@dimen/dp_48"

android:text="切换城市"

android:foreground="@drawable/bg_white"

android:textColor="@color/black"

android:textSize="@dimen/sp_16"/>

<TextView

android:id="@+id/tv_change_bg"

android:gravity="center"

android:layout_width="@dimen/dp_140"

android:layout_height="@dimen/dp_48"

android:text="切换背景"

android:foreground="@drawable/bg_white"

android:textColor="@color/black"

android:textSize="@dimen/sp_16"/>

<TextView

android:id="@+id/tv_more"

android:gravity="center"

android:layout_width="@dimen/dp_140"

android:layout_height="@dimen/dp_48"

android:text="更多功能"

android:foreground="@drawable/bg_white"

android:textColor="@color/black"

android:textSize="@dimen/sp_16"/>

然后是对activity_main.xml文件的修改

在这里插入图片描述

这里修改了原来的id和src里面的图片,增加了点击的效果

icon_add.png

在这里插入图片描述

selector_bg_img.xml,点击之后背景变色,增加用户体验

MainActivity.java

在这里插入图片描述

这里我是把原来的id注释掉,不过我没有删掉,因为我要让你们知道是怎么样一个过程,你们是可以直接替换的,不替换会报错了,当然你不改Id就不会报错,但是id的命名和现在是意思对不上,会对其他人造成困扰,严谨一点就修改ID。

在这里插入图片描述

更改点击之后的弹窗。

在这里插入图片描述

要显示弹窗一些基本的配置必不可少,这里用到了一个动画工具类AnimationUtil,代码如下:

package com.llw.mvplibrary.utils;

import android.animation.Animator;

import android.animation.ValueAnimator;

import android.view.animation.Interpolator;

import android.view.animation.LinearInterpolator;

/**

  • 动画工具类

  • UpdateListener: 动画过程中通过添加此监听来回调数据

  • EndListener: 动画结束的时候通过此监听器来做一些处理

*/

public class AnimationUtil {

private ValueAnimator valueAnimator;

private UpdateListener updateListener;

private EndListener endListener;

private long duration;

private float start;

private float end;

private Interpolator interpolator = new LinearInterpolator();

public AnimationUtil() {

duration = 1000; //默认动画时常1s

start = 0.0f;

end = 1.0f;

interpolator = new LinearInterpolator();// 匀速的插值器

}

public void setDuration(int timeLength) {

duration = timeLength;

}

public void setValueAnimator(float start, float end, long duration) {

this.start = start;

this.end = end;

this.duration = duration;

}

public void setInterpolator(Interpolator interpolator) {

this.interpolator = interpolator;

}

public void startAnimator() {

if (valueAnimator != null){

valueAnimator = null;

}

valueAnimator = ValueAnimator.ofFloat(start, end);

valueAnimator.setDuration(duration);

valueAnimator.setInterpolator(interpolator);

valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

@Override

public void onAnimationUpdate(ValueAnimator valueAnimator) {

if (updateListener == null) {

return;

}

float cur = (float) valueAnimator.getAnimatedValue();

updateListener.progress(cur);

}

});

valueAnimator.addListener(new Animator.AnimatorListener() {

@Override

public void onAnimationStart(Animator animator) {}

@Override

public void onAnimationEnd(Animator animator) {

if(endListener == null){

return;

}

endListener.endUpdate(animator);

}

@Override

public void onAnimationCancel(Animator animator) {}

@Override

public void onAnimationRepeat(Animator animator) {}

});

valueAnimator.start();

}

public void addUpdateListener(UpdateListener updateListener) {

this.updateListener = updateListener;

}

public void addEndListner(EndListener endListener){

this.endListener = endListener;

}

public interface EndListener {

void endUpdate(Animator animator);

}

public interface UpdateListener {

void progress(float progress);

}

}

在这里插入图片描述

然后写三个方法,一个显示弹窗,及控制里面的点击事件、计算动画时间、第三个修改背景的透明度类似蒙版的效果。

在这里插入图片描述

这三个方法的代码我都会贴上来。不过首先,先增加弹窗出现和关闭的动画效果。

在这里插入图片描述

这张图告诉你在什么地方添加这个样式

<item name="android:windowEnterAnimation">@anim/pop_add_show</item> <item name="android:windowExitAnimation">@anim/pop_add_hide</item>

pop_add_show.xml 显示动画

<alpha

android:duration="500"

android:fromAlpha="0.0"

android:toAlpha="1.0"/>

<scale

android:duration="500"

android:fromXScale="0"

android:fromYScale="0"

android:interpolator="@android:anim/decelerate_interpolator"

android:pivotX="85%"

android:pivotY="0%"

android:toXScale="1.0"

android:toYScale="1.0"/>

pop_add_hide.xml 隐藏动画

<alpha

android:duration="500"

android:fromAlpha="1.0"

android:toAlpha="0.0"/>

<scale

android:duration="500"

android:fromXScale="1.0"

android:fromYScale="1.0"

android:interpolator="@android:anim/accelerate_interpolator"

android:pivotX="85%"

android:pivotY="0%"

android:toXScale="0"

android:toYScale="0"/>

然后贴一下三个方法的代码:

showAddWindow方法

/**

  • 更多功能弹窗,因为区别于我原先写的弹窗

*/

private void showAddWindow() {

// 设置布局文件

mPopupWindow.setContentView(LayoutInflater.from(this).inflate(R.layout.window_add, null));// 为了避免部分机型不显示,我们需要重新设置一下宽高

mPopupWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);

mPopupWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);

mPopupWindow.setBackgroundDrawable(new ColorDrawable(0x0000));// 设置pop透明效果

mPopupWindow.setAnimationStyle(R.style.pop_add);// 设置pop出入动画

mPopupWindow.setFocusable(true);// 设置pop获取焦点,如果为false点击返回按钮会退出当前Activity,如果pop中有Editor的话,focusable必须要为true

mPopupWindow.setTouchable(true);// 设置pop可点击,为false点击事件无效,默认为true

mPopupWindow.setOutsideTouchable(true);// 设置点击pop外侧消失,默认为false;在focusable为true时点击外侧始终消失

mPopupWindow.showAsDropDown(ivAdd, -100, 0);// 相对于 + 号正下面,同时可以设置偏移量

// 设置pop关闭监听,用于改变背景透明度

mPopupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {//关闭弹窗

@Override

public void onDismiss() {

toggleBright();

}

});

//绑定布局中的控件

TextView changeCity = mPopupWindow.getContentView().findViewById(R.id.tv_change_city);

TextView changeBg = mPopupWindow.getContentView().findViewById(R.id.tv_change_bg);

TextView more = mPopupWindow.getContentView().findViewById(R.id.tv_more);

changeCity.setOnClickListener(view -> {//切换城市

showCityWindow();

mPopupWindow.dismiss();

});

changeBg.setOnClickListener(view -> {//切换背景

ToastUtils.showShortToast(context,"你点击了切换背景");

mPopupWindow.dismiss();

});

more.setOnClickListener(view -> {//更多功能

ToastUtils.showShortToast(context,"如果你有什么好的建议,可以博客留言哦!");

mPopupWindow.dismiss();

});

}

计算动画时间

/**

  • 计算动画时间

*/

private void toggleBright() {

// 三个参数分别为:起始值 结束值 时长,那么整个动画回调过来的值就是从0.5f--1f的

animUtil.setValueAnimator(START_ALPHA, END_ALPHA, DURATION);

animUtil.addUpdateListener(new AnimationUtil.UpdateListener() {

@Override

public void progress(float progress) {

// 此处系统会根据上述三个值,计算每次回调的值是多少,我们根据这个值来改变透明度

bgAlpha = bright ? progress : (START_ALPHA + END_ALPHA - progress);

backgroundAlpha(bgAlpha);

}

});

animUtil.addEndListner(new AnimationUtil.EndListener() {

@Override

public void endUpdate(Animator animator) {

// 在一次动画结束的时候,翻转状态

bright = !bright;

}

});

animUtil.startAnimator();

}

此方法用于改变背景的透明度

/**

  • 此方法用于改变背景的透明度,从而达到“变暗”的效果

*/

private void backgroundAlpha(float bgAlpha) {

WindowManager.LayoutParams lp = getWindow().getAttributes();

// 0.0-1.0

lp.alpha = bgAlpha;

getWindow().setAttributes(lp);

// everything behind this window will be dimmed.

// 此方法用来设置浮动层,防止部分手机变暗无效

getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);

}

这几个方法借鉴了网络上的代码。

效果图如下:

在这里插入图片描述

如果你在写的过程中遇到任何问题,可以直接评论或者给我发邮件。

接下来就是切换背景了

切换背景

切换背景的业务代码,我当然不可能也在MainActivity中写,因为现在里面的代码已经够多了,所以就要新建一个页面。在项目的包下新建一个ui包,用于存放除MainActivity之外的所有Activity。这样会比较规范,至于为什么不把MainActivity也放进去,因为目前我还不想放进去。

鼠标右键点击ui → New → Activity → Empty Activity

在这里插入图片描述

在这里插入图片描述

Next即可

在这里插入图片描述

创建好之后修改布局。

修改布局之前,这个要更改一个开关按钮的样式。

在这里插入图片描述

首先是增加样式代码:

然后自定义View

在这里插入图片描述

SwitchButton.java代码如下:

package com.llw.mvplibrary.view;

import android.animation.Animator;

import android.animation.ValueAnimator;

import android.annotation.TargetApi;

import android.content.Context;

import android.content.res.Resources;

import android.content.res.TypedArray;

import android.graphics.Canvas;

import android.graphics.Color;

import android.graphics.Paint;

import android.graphics.RectF;

import android.os.Build;

import android.util.AttributeSet;

import android.util.TypedValue;

import android.view.MotionEvent;

import android.view.View;

import android.widget.Checkable;

import com.llw.mvplibrary.R;

/**

  • SwitchButton.

*/

public class SwitchButton extends View implements Checkable {

private static final int DEFAULT_WIDTH = dp2pxInt(58);

private static final int DEFAULT_HEIGHT = dp2pxInt(36);

/**

  • 动画状态:

  • 1.静止

  • 2.进入拖动

  • 3.处于拖动

  • 4.拖动-复位

  • 5.拖动-切换

  • 6.点击切换

  • **/

private final int ANIMATE_STATE_NONE = 0;

private final int ANIMATE_STATE_PENDING_DRAG = 1;

private final int ANIMATE_STATE_DRAGING = 2;

private final int ANIMATE_STATE_PENDING_RESET = 3;

private final int ANIMATE_STATE_PENDING_SETTLE = 4;

private final int ANIMATE_STATE_SWITCH = 5;

public SwitchButton(Context context) {

super(context);

init(context, null);

}

public SwitchButton(Context context, AttributeSet attrs) {

super(context, attrs);

init(context, attrs);

}

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

super(context, attrs, defStyleAttr);

init(context, attrs);

}

@TargetApi(Build.VERSION_CODES.LOLLIPOP)

public SwitchButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {

super(context, attrs, defStyleAttr, defStyleRes);

init(context, attrs);

}

@Override

public final void setPadding(int left, int top, int right, int bottom) {

super.setPadding(0, 0, 0, 0);

}

/**

  • 初始化参数

*/

private void init(Context context, AttributeSet attrs) {

TypedArray typedArray = null;

if(attrs != null){

typedArray = context.obtainStyledAttributes(attrs, R.styleable.SwitchButton);

}

shadowEffect = optBoolean(typedArray,

R.styleable.SwitchButton_sb_shadow_effect,

true);

uncheckCircleColor = optColor(typedArray,

R.styleable.SwitchButton_sb_uncheckcircle_color,

0XffAAAAAA);//0XffAAAAAA;

uncheckCircleWidth = optPixelSize(typedArray,

R.styleable.SwitchButton_sb_uncheckcircle_width,

dp2pxInt(1.5f));//dp2pxInt(1.5f);

uncheckCircleOffsetX = dp2px(10);

uncheckCircleRadius = optPixelSize(typedArray,

R.styleable.SwitchButton_sb_uncheckcircle_radius,

dp2px(4));//dp2px(4);

checkedLineOffsetX = dp2px(4);

checkedLineOffsetY = dp2px(4);

shadowRadius = optPixelSize(typedArray,

R.styleable.SwitchButton_sb_shadow_radius,

dp2pxInt(2.5f));//dp2pxInt(2.5f);

shadowOffset = optPixelSize(typedArray,

R.styleable.SwitchButton_sb_shadow_offset,

dp2pxInt(1.5f));//dp2pxInt(1.5f);

shadowColor = optColor(typedArray,

R.styleable.SwitchButton_sb_shadow_color,

0X33000000);//0X33000000;

uncheckColor = optColor(typedArray,

R.styleable.SwitchButton_sb_uncheck_color,

0XffDDDDDD);//0XffDDDDDD;

checkedColor = optColor(typedArray,

R.styleable.SwitchButton_sb_checked_color,

0Xff51d367);//0Xff51d367;

borderWidth = optPixelSize(typedArray,

R.styleable.SwitchButton_sb_border_width,

dp2pxInt(1));//dp2pxInt(1);

checkLineColor = optColor(typedArray,

R.styleable.SwitchButton_sb_checkline_color,

Color.WHITE);//Color.WHITE;

checkLineWidth = optPixelSize(typedArray,

R.styleable.SwitchButton_sb_checkline_width,

dp2pxInt(1f));//dp2pxInt(1.0f);

checkLineLength = dp2px(6);

int buttonColor = optColor(typedArray,

R.styleable.SwitchButton_sb_button_color,

Color.WHITE);//Color.WHITE;

int effectDuration = optInt(typedArray,

R.styleable.SwitchButton_sb_effect_duration,

300);//300;

isChecked = optBoolean(typedArray,

R.styleable.SwitchButton_sb_checked,

false);

showIndicator = optBoolean(typedArray,

R.styleable.SwitchButton_sb_show_indicator,

true);

background = optColor(typedArray,

R.styleable.SwitchButton_sb_background,

Color.WHITE);//Color.WHITE;

enableEffect = optBoolean(typedArray,

R.styleable.SwitchButton_sb_enable_effect,

true);

if(typedArray != null){

typedArray.recycle();

}

paint = new Paint(Paint.ANTI_ALIAS_FLAG);

buttonPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

buttonPaint.setColor(buttonColor);

if(shadowEffect){

buttonPaint.setShadowLayer(

shadowRadius,

0, shadowOffset,

shadowColor);

}

viewState = new ViewState();

beforeState = new ViewState();

afterState = new ViewState();

valueAnimator = ValueAnimator.ofFloat(0f, 1f);

valueAnimator.setDuration(effectDuration);

valueAnimator.setRepeatCount(0);

valueAnimator.addUpdateListener(animatorUpdateListener);

valueAnimator.addListener(animatorListener);

super.setClickable(true);

this.setPadding(0, 0, 0, 0);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {

setLayerType(LAYER_TYPE_SOFTWARE, null);

}

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

final int widthMode = MeasureSpec.getMode(widthMeasureSpec);

final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

if(widthMode == MeasureSpec.UNSPECIFIED

|| widthMode == MeasureSpec.AT_MOST){

widthMeasureSpec = MeasureSpec.makeMeasureSpec(DEFAULT_WIDTH, MeasureSpec.EXACTLY);

}

if(heightMode == MeasureSpec.UNSPECIFIED

|| heightMode == MeasureSpec.AT_MOST){

heightMeasureSpec = MeasureSpec.makeMeasureSpec(DEFAULT_HEIGHT, MeasureSpec.EXACTLY);

}

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

}

@Override

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

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

float viewPadding = Math.max(shadowRadius + shadowOffset, borderWidth);

height = h - viewPadding - viewPadding;

width = w - viewPadding - viewPadding;

viewRadius = height * .5f;

buttonRadius = viewRadius - borderWidth;

left = viewPadding;

top = viewPadding;

right = w - viewPadding;

bottom = h - viewPadding;

centerX = (left + right) * .5f;

centerY = (top + bottom) * .5f;

buttonMinX = left + viewRadius;

buttonMaxX = right - viewRadius;

if(isChecked()){

setCheckedViewState(viewState);

}else{

setUncheckViewState(viewState);

}

isUiInited = true;

postInvalidate();

}

/**

  • @param viewState

*/

private void setUncheckViewState(ViewState viewState){

viewState.radius = 0;

viewState.checkStateColor = uncheckColor;

viewState.checkedLineColor = Color.TRANSPARENT;

viewState.buttonX = buttonMinX;

}

/**

  • @param viewState

*/

private void setCheckedViewState(ViewState viewState){

viewState.radius = viewRadius;

viewState.checkStateColor = checkedColor;

viewState.checkedLineColor = checkLineColor;

viewState.buttonX = buttonMaxX;

}

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

paint.setStrokeWidth(borderWidth);

paint.setStyle(Paint.Style.FILL);

//绘制白色背景

paint.setColor(background);

drawRoundRect(canvas,

left, top, right, bottom,

viewRadius, paint);

//绘制关闭状态的边框

paint.setStyle(Paint.Style.STROKE);

paint.setColor(uncheckColor);

drawRoundRect(canvas,

left, top, right, bottom,

viewRadius, paint);

//绘制小圆圈

if(showIndicator){

drawUncheckIndicator(canvas);

}

//绘制开启背景色

float des = viewState.radius * .5f;//[0-backgroundRadius*0.5f]

paint.setStyle(Paint.Style.STROKE);

paint.setColor(viewState.checkStateColor);

paint.setStrokeWidth(borderWidth + des * 2f);

drawRoundRect(canvas,

left + des, top + des, right - des, bottom - des,

viewRadius, paint);

//绘制按钮左边绿色长条遮挡

paint.setStyle(Paint.Style.FILL);

paint.setStrokeWidth(1);

drawArc(canvas,

left, top,

left + 2 * viewRadius, top + 2 * viewRadius,

90, 180, paint);

canvas.drawRect(

left + viewRadius, top,

viewState.buttonX, top + 2 * viewRadius,

paint);

//绘制小线条

if(showIndicator){

drawCheckedIndicator(canvas);

}

//绘制按钮

drawButton(canvas, viewState.buttonX, centerY);

}

/**

  • 绘制选中状态指示器

  • @param canvas

*/

protected void drawCheckedIndicator(Canvas canvas) {

drawCheckedIndicator(canvas,

viewState.checkedLineColor,

checkLineWidth,

left + viewRadius - checkedLineOffsetX, centerY - checkLineLength,

left + viewRadius - checkedLineOffsetY, centerY + checkLineLength,

paint);

}

/**

  • 绘制选中状态指示器

  • @param canvas

  • @param color

  • @param lineWidth

  • @param sx

  • @param sy

  • @param ex

  • @param ey

  • @param paint

*/

protected void drawCheckedIndicator(Canvas canvas,

int color,

float lineWidth,

float sx, float sy, float ex, float ey,

Paint paint) {

paint.setStyle(Paint.Style.STROKE);

paint.setColor(color);

paint.setStrokeWidth(lineWidth);

canvas.drawLine(

sx, sy, ex, ey,

paint);

}

/**

  • 绘制关闭状态指示器

  • @param canvas

*/

private void drawUncheckIndicator(Canvas canvas) {

drawUncheckIndicator(canvas,

uncheckCircleColor,

uncheckCircleWidth,

right - uncheckCircleOffsetX, centerY,

uncheckCircleRadius,

paint);

}

/**

  • 绘制关闭状态指示器

  • @param canvas

  • @param color

  • @param lineWidth

  • @param centerX

  • @param centerY

  • @param radius

  • @param paint

*/

protected void drawUncheckIndicator(Canvas canvas,

int color,

float lineWidth,

float centerX, float centerY,

float radius,

Paint paint) {

paint.setStyle(Paint.Style.STROKE);

paint.setColor(color);

paint.setStrokeWidth(lineWidth);

canvas.drawCircle(centerX, centerY, radius, paint);

}

/**

  • @param canvas

  • @param left

  • @param top

  • @param right

  • @param bottom

  • @param startAngle

  • @param sweepAngle

  • @param paint

*/

private void drawArc(Canvas canvas,

float left, float top,

float right, float bottom,

float startAngle, float sweepAngle,

Paint paint){

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {

canvas.drawArc(left, top, right, bottom,

startAngle, sweepAngle, true, paint);

}else{

rect.set(left, top, right, bottom);

canvas.drawArc(rect,

startAngle, sweepAngle, true, paint);

}

}

/**

  • @param canvas

  • @param left

  • @param top

  • @param right

  • @param bottom

  • @param backgroundRadius

  • @param paint

*/

private void drawRoundRect(Canvas canvas,

float left, float top,

float right, float bottom,

float backgroundRadius,

Paint paint){

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {

canvas.drawRoundRect(left, top, right, bottom,

backgroundRadius, backgroundRadius, paint);

}else{

rect.set(left, top, right, bottom);

canvas.drawRoundRect(rect,

backgroundRadius, backgroundRadius, paint);

}

}

/**

  • @param canvas

  • @param x px

  • @param y px

*/

private void drawButton(Canvas canvas, float x, float y) {

canvas.drawCircle(x, y, buttonRadius, buttonPaint);

paint.setStyle(Paint.Style.STROKE);

paint.setStrokeWidth(1);

paint.setColor(0XffDDDDDD);

canvas.drawCircle(x, y, buttonRadius, paint);

}

@Override

public void setChecked(boolean checked) {

if(checked == isChecked()){

postInvalidate();

return;

}

toggle(enableEffect, false);

}

@Override

public boolean isChecked() {

return isChecked;

}

@Override

public void toggle() {

toggle(true);

}

/**

  • 切换状态

  • @param animate

*/

public void toggle(boolean animate) {

toggle(animate, true);

}

private void toggle(boolean animate, boolean broadcast) {

if(!isEnabled()){return;}

if(isEventBroadcast){

throw new RuntimeException("should NOT switch the state in method: [onCheckedChanged]!");

}

if(!isUiInited){

isChecked = !isChecked;

if(broadcast){

broadcastEvent();

}

return;

}

if(valueAnimator.isRunning()){

valueAnimator.cancel();

}

if(!enableEffect || !animate){

isChecked = !isChecked;

if(isChecked()){

setCheckedViewState(viewState);

}else{

setUncheckViewState(viewState);

}

postInvalidate();

if(broadcast){

broadcastEvent();

}

return;

}

animateState = ANIMATE_STATE_SWITCH;

beforeState.copy(viewState);

if(isChecked()){

//切换到unchecked

setUncheckViewState(afterState);

}else{

setCheckedViewState(afterState);

}

valueAnimator.start();

}

/**

*/

private void broadcastEvent() {

if(onCheckedChangeListener != null){

isEventBroadcast = true;

onCheckedChangeListener.onCheckedChanged(this, isChecked());

}

isEventBroadcast = false;

}

@Override

public boolean onTouchEvent(MotionEvent event) {

if(!isEnabled()){return false;}

int actionMasked = event.getActionMasked();

switch (actionMasked){

case MotionEvent.ACTION_DOWN:{

isTouchingDown = true;

touchDownTime = System.currentTimeMillis();

//取消准备进入拖动状态

removeCallbacks(postPendingDrag);

//预设100ms进入拖动状态

postDelayed(postPendingDrag, 100);

break;

}

case MotionEvent.ACTION_MOVE:{

float eventX = event.getX();

if(isPendingDragState()){

//在准备进入拖动状态过程中,可以拖动按钮位置

float fraction = eventX / getWidth();

fraction = Math.max(0f, Math.min(1f, fraction));

viewState.buttonX = buttonMinX

  • (buttonMaxX - buttonMinX)
  • fraction;

}else if(isDragState()){

//拖动按钮位置,同时改变对应的背景颜色

float fraction = eventX / getWidth();

fraction = Math.max(0f, Math.min(1f, fraction));

viewState.buttonX = buttonMinX

  • (buttonMaxX - buttonMinX)
  • fraction;

viewState.checkStateColor = (int) argbEvaluator.evaluate(

fraction,

uncheckColor,

checkedColor

);

postInvalidate();

}

break;

}

case MotionEvent.ACTION_UP:{

isTouchingDown = false;

//取消准备进入拖动状态

removeCallbacks(postPendingDrag);

if(System.currentTimeMillis() - touchDownTime <= 300){

//点击时间小于300ms,认为是点击操作

toggle();

}else if(isDragState()){

//在拖动状态,计算按钮位置,设置是否切换状态

float eventX = event.getX();

float fraction = eventX / getWidth();

fraction = Math.max(0f, Math.min(1f, fraction));

boolean newCheck = fraction > .5f;

if(newCheck == isChecked()){

pendingCancelDragState();

}else{

isChecked = newCheck;

pendingSettleState();

}

}else if(isPendingDragState()){

//在准备进入拖动状态过程中,取消之,复位

pendingCancelDragState();

}

break;

}

case MotionEvent.ACTION_CANCEL:{

isTouchingDown = false;

removeCallbacks(postPendingDrag);

if(isPendingDragState()

|| isDragState()){

//复位

pendingCancelDragState();

}

break;

}

}

return true;

}

/**

  • 是否在动画状态

  • @return

*/

private boolean isInAnimating(){

return animateState != ANIMATE_STATE_NONE;

}

/**

  • 是否在进入拖动或离开拖动状态

  • @return

*/

private boolean isPendingDragState(){

return animateState == ANIMATE_STATE_PENDING_DRAG

|| animateState == ANIMATE_STATE_PENDING_RESET;

}

/**

  • 是否在手指拖动状态

  • @return

*/

private boolean isDragState(){

return animateState == ANIMATE_STATE_DRAGING;

}

/**

  • 设置是否启用阴影效果

  • @param shadowEffect true.启用

*/

public void setShadowEffect(boolean shadowEffect) {

if(this.shadowEffect == shadowEffect){return;}

this.shadowEffect = shadowEffect;

if(this.shadowEffect){

buttonPaint.setShadowLayer(

shadowRadius,

0, shadowOffset,

shadowColor);

}else{

buttonPaint.setShadowLayer(

0,

0, 0,

0);

}

}

public void setEnableEffect(boolean enable){

this.enableEffect = enable;

}

/**

  • 开始进入拖动状态

*/

private void pendingDragState() {

if(isInAnimating()){return;}

if(!isTouchingDown){return;}

if(valueAnimator.isRunning()){

valueAnimator.cancel();

}

animateState = ANIMATE_STATE_PENDING_DRAG;

beforeState.copy(viewState);

afterState.copy(viewState);

if(isChecked()){

afterState.checkStateColor = checkedColor;

afterState.buttonX = buttonMaxX;

afterState.checkedLineColor = checkedColor;

}else{

afterState.checkStateColor = uncheckColor;

afterState.buttonX = buttonMinX;

afterState.radius = viewRadius;

}

valueAnimator.start();

}

/**

  • 取消拖动状态

*/

private void pendingCancelDragState() {

if(isDragState() || isPendingDragState()){

if(valueAnimator.isRunning()){

valueAnimator.cancel();

}

animateState = ANIMATE_STATE_PENDING_RESET;

beforeState.copy(viewState);

if(isChecked()){

setCheckedViewState(afterState);

}else{

setUncheckViewState(afterState);

}

valueAnimator.start();

}

}

/**

  • 动画-设置新的状态

*/

private void pendingSettleState() {

if(valueAnimator.isRunning()){

valueAnimator.cancel();

}

animateState = ANIMATE_STATE_PENDING_SETTLE;

beforeState.copy(viewState);

if(isChecked()){

setCheckedViewState(afterState);

}else{

setUncheckViewState(afterState);

}

valueAnimator.start();

}

@Override

public final void setOnClickListener(OnClickListener l) {}

@Override

public final void setOnLongClickListener(OnLongClickListener l) {}

public void setOnCheckedChangeListener(OnCheckedChangeListener l){

onCheckedChangeListener = l;

}

public interface OnCheckedChangeListener{

void onCheckedChanged(SwitchButton view, boolean isChecked);

}

/*******************************************************/

private static float dp2px(float dp){

Resources r = Resources.getSystem();

return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, r.getDisplayMetrics());

}

private static int dp2pxInt(float dp){

return (int) dp2px(dp);

}

private static int optInt(TypedArray typedArray,

int index,

int def) {

if(typedArray == null){return def;}

return typedArray.getInt(index, def);

}

private static float optPixelSize(TypedArray typedArray,

int index,

float def) {

if(typedArray == null){return def;}

return typedArray.getDimension(index, def);

}

private static int optPixelSize(TypedArray typedArray,

int index,

int def) {

if(typedArray == null){return def;}

return typedArray.getDimensionPixelOffset(index, def);

}

private static int optColor(TypedArray typedArray,

int index,

int def) {

if(typedArray == null){return def;}

return typedArray.getColor(index, def);

}

private static boolean optBoolean(TypedArray typedArray,

int index,

boolean def) {

if(typedArray == null){return def;}

return typedArray.getBoolean(index, def);

}

/*******************************************************/

/**

  • 阴影半径

*/

private int shadowRadius;

/**

  • 阴影Y偏移px

*/

private int shadowOffset;

/**

  • 阴影颜色

*/

private int shadowColor ;

/**

  • 背景半径

*/

private float viewRadius;

/**

  • 按钮半径

*/

private float buttonRadius;

/**

  • 背景高

*/

private float height ;

/**

  • 背景宽

*/

private float width;

/**

  • 背景位置

*/

private float left ;

private float top ;

private float right ;

private float bottom ;

private float centerX;

private float centerY;

/**

  • 背景底色

*/

private int background;

/**

  • 背景关闭颜色

*/

private int uncheckColor;

/**

  • 背景打开颜色

*/

private int checkedColor;

/**

  • 边框宽度px

*/

private int borderWidth;

/**

  • 打开指示线颜色

*/

private int checkLineColor;

/**

  • 打开指示线宽

*/

private int checkLineWidth;

/**

  • 打开指示线长

*/

private float checkLineLength;

/**

  • 关闭圆圈颜色

*/

private int uncheckCircleColor;

/**

*关闭圆圈线宽

*/

private int uncheckCircleWidth;

/**

*关闭圆圈位移X

*/

private float uncheckCircleOffsetX;

/**

*关闭圆圈半径

*/

private float uncheckCircleRadius;

/**

*打开指示线位移X

*/

private float checkedLineOffsetX;

/**

*打开指示线位移Y

*/

private float checkedLineOffsetY;

/**

  • 按钮最左边

*/

private float buttonMinX;

/**

  • 按钮最右边

*/

private float buttonMaxX;

/**

  • 按钮画笔

*/

private Paint buttonPaint;

/**

  • 背景画笔

*/

private Paint paint;

/**

  • 当前状态

*/

private ViewState viewState;

private ViewState beforeState;

private ViewState afterState;

private RectF rect = new RectF();

/**

  • 动画状态

*/

private int animateState = ANIMATE_STATE_NONE;

/**

*/

private ValueAnimator valueAnimator;

private final android.animation.ArgbEvaluator argbEvaluator

= new android.animation.ArgbEvaluator();

/**

*是否选中

*/

private boolean isChecked;

/**

  • 是否启用动画

*/

private boolean enableEffect;

/**

  • 是否启用阴影效果

*/

private boolean shadowEffect;

/**

  • 是否显示指示器

*/

private boolean showIndicator;

/**

  • 收拾是否按下

*/

private boolean isTouchingDown = false;

/**

*/

private boolean isUiInited = false;

/**

*/

private boolean isEventBroadcast = false;

private OnCheckedChangeListener onCheckedChangeListener;

/**

  • 手势按下的时刻

*/

private long touchDownTime;

private Runnable postPendingDrag = new Runnable() {

@Override

public void run() {

if(!isInAnimating()){

pendingDragState();

}

}

};

private ValueAnimator.AnimatorUpdateListener animatorUpdateListener

= new ValueAnimator.AnimatorUpdateListener() {

@Override

public void onAnimationUpdate(ValueAnimator animation) {

float value = (Float) animation.getAnimatedValue();

switch (animateState) {

case ANIMATE_STATE_PENDING_SETTLE: {

}

case ANIMATE_STATE_PENDING_RESET: {

}

case ANIMATE_STATE_PENDING_DRAG: {

viewState.checkedLineColor = (int) argbEvaluator.evaluate(

value,

beforeState.checkedLineColor,

afterState.checkedLineColor

);

viewState.radius = beforeState.radius

  • (afterState.radius - beforeState.radius) * value;

if(animateState != ANIMATE_STATE_PENDING_DRAG){

viewState.buttonX = beforeState.buttonX

  • (afterState.buttonX - beforeState.buttonX) * value;

}

viewState.checkStateColor = (int) argbEvaluator.evaluate(

value,

beforeState.checkStateColor,

afterState.checkStateColor

);

break;

}

case ANIMATE_STATE_SWITCH: {

viewState.buttonX = beforeState.buttonX

  • (afterState.buttonX - beforeState.buttonX) * value;

float fraction = (viewState.buttonX - buttonMinX) / (buttonMaxX - buttonMinX);

viewState.checkStateColor = (int) argbEvaluator.evaluate(

fraction,

uncheckColor,

checkedColor

);

viewState.radius = fraction * viewRadius;

viewState.checkedLineColor = (int) argbEvaluator.evaluate(

fraction,

Color.TRANSPARENT,

checkLineColor

);

break;

}

default:

case ANIMATE_STATE_DRAGING: {

}

case ANIMATE_STATE_NONE: {

break;

}

}

postInvalidate();

}

};

private Animator.AnimatorListener animatorListener

= new Animator.AnimatorListener() {

@Override

public void onAnimationStart(Animator animation) {

}

@Override

public void onAnimationEnd(Animator animation) {

switch (animateState) {

case ANIMATE_STATE_DRAGING: {

break;

}

case ANIMATE_STATE_PENDING_DRAG: {

animateState = ANIMATE_STATE_DRAGING;

viewState.checkedLineColor = Color.TRANSPARENT;

viewState.radius = viewRadius;

postInvalidate();

break;

}

case ANIMATE_STATE_PENDING_RESET: {

animateState = ANIMATE_STATE_NONE;

postInvalidate();

break;

}

case ANIMATE_STATE_PENDING_SETTLE: {

animateState = ANIMATE_STATE_NONE;

postInvalidate();

broadcastEvent();

break;

}

case ANIMATE_STATE_SWITCH: {

isChecked = !isChecked;

最后

我见过很多技术leader在面试的时候,遇到处于迷茫期的大龄程序员,比面试官年龄都大。这些人有一些共同特征:可能工作了5、6年,还是每天重复给业务部门写代码,工作内容的重复性比较高,没有什么技术含量的工作。问到这些人的职业规划时,他们也没有太多想法。

其实30岁到40岁是一个人职业发展的黄金阶段,一定要在业务范围内的扩张,技术广度和深度提升上有自己的计划,才有助于在职业发展上有持续的发展路径,而不至于停滞不前。

不断奔跑,你就知道学习的意义所在!

《Android高级架构师面试指导+2021大厂面试真题》免费领取