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大厂面试真题》免费领取