[TOC]
效果图
一个自用的登陆动画,可以自定义背景颜色、文本、选中颜色、动画时间等。需要android21以上版本。
实现步骤
自定义登陆按钮
登陆按钮的绘制主要有以下几点:
- 绘制按钮的静态背景及文本:这个没什么好说的,就是canvas的使用。
- 按钮的焦点状态:通过onFocusChanged方法来判断按钮是否获得焦点,不再tv上使用可以无视这个。
- 按钮的缩短动画:通过属性动画(值动画)获取一个不断改变的状态值,同时不断刷新view实现动画效果。
- 按钮的旋转动画:就是一个普通的旋转补间动画RotateAnimation。
- 登陆失败时的回置动画:同缩短动画。
文笔比较渣,还是直接看代码吧,比较简单,也加了注解。
public class LoginButton extends View {
private final int duration = 300;//按钮缩短动画的持续时间
private Paint paintBg;
private Paint paintText;
private Paint paintCircle;
private int startWidth;//初始宽度
private int startHeight;//初始高度
private int marginLength;//缩进距离
private boolean drawCircle = false;//绘制圆还是文本
private int bgColor;
private int bgColorFocused;
private int textColor;
private String text;
private int textSize;
private int textWidth;
private int textHeight;
private Loading loading;
public void setLoading(Loading loading) {
this.loading = loading;
}
public LoginButton(Context context) {
super(context, null);
}
public LoginButton(Context context, AttributeSet attrs) {
super(context, attrs);
//自定义属性
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.LoginButton);
bgColor = array.getColor(R.styleable.LoginButton_bgColor, Color.RED);
bgColorFocused = array.getColor(R.styleable.LoginButton_bgColorFocused, 0);
textColor = array.getColor(R.styleable.LoginButton_textColor, Color.WHITE);
text = array.getString(R.styleable.LoginButton_text);
textSize = array.getDimensionPixelSize(R.styleable.LoginButton_textSize, 24);
array.recycle();
init();
}
private void init() {
//设置按钮可点击,可获得焦点
setClickable(true);
setFocusable(true);
paintBg = new Paint();
paintBg.setColor(bgColor);
paintBg.setStyle(Paint.Style.FILL);
paintBg.setAntiAlias(true);
paintText = new Paint();
paintText.setColor(textColor);
paintText.setTextSize(textSize);
Rect rect = new Rect();
paintText.getTextBounds(text, 0, text.length(), rect);
textWidth = rect.width();
textHeight = rect.height();
paintCircle = new Paint();
paintCircle.setColor(textColor);
paintCircle.setStrokeWidth(3);
paintCircle.setStyle(Paint.Style.STROKE);
paintCircle.setAntiAlias(true);
}
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
if (hasWindowFocus) {
startWidth = getMeasuredWidth();
startHeight = getMeasuredHeight();
invalidate();
}
}
//如果不在tv上使用,可以不加这段
@Override
protected void onFocusChanged(boolean gainFocus, int direction, @Nullable Rect previouslyFocusedRect) {
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
if (gainFocus && bgColorFocused != 0)
paintBg.setColor(bgColorFocused);
else
paintBg.setColor(bgColor);
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawBg(canvas);
if (drawCircle)
drawCircle(canvas);
else
drawText(canvas);
}
//绘制圆,动画
private void drawCircle(Canvas canvas) {
RectF oval = new RectF();
oval.left = startWidth / 2 - startHeight / 2 + startHeight / 5;
oval.right = oval.left + startHeight / 5 * 3;
oval.top = startHeight / 5;
oval.bottom = oval.top + startHeight / 5 * 3;
canvas.drawArc(oval, 0, 120, false, paintCircle);
}
//绘制文本
private void drawText(Canvas canvas) {
canvas.drawText(text, startWidth / 2 - textWidth / 2, startHeight / 2 + textHeight / 2, paintText);
}
//绘制背景
private void drawBg(Canvas canvas) {
int left = marginLength;
int top = 0;
int right = startWidth - marginLength;
int bottom = startHeight;
RectF rectF = new RectF(left, top, right, bottom);
//绘制圆角矩形 1.范围 2.x方向的圆角半径 3.y方向的圆角半径 4.画笔
canvas.drawRoundRect(rectF, startHeight / 2, startHeight / 2, paintBg);
}
/**
* 点击按钮后启动动画
*/
public void click() {
setClickable(false);
float scale = startHeight / (float) startWidth;
ValueAnimator animator = ValueAnimator.ofFloat(1, scale);
animator.setDuration(duration);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float flag = (float) animation.getAnimatedValue();
marginLength = (int) (startWidth * (1 - flag)) / 2;
if ((startWidth - marginLength * 2) < textWidth * 1.5)
drawCircle = true;
invalidate();
}
});
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
startCircleAnim();
if (loading != null)
loading.startLoading();
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
animator.start();
}
/**
* 登陆失败后回置
*/
public void reset() {
clearAnimation();
float scale = startHeight / (float) startWidth;
ValueAnimator animator = ValueAnimator.ofFloat(scale, 1);
animator.setDuration(duration);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float flag = (float) animation.getAnimatedValue();
marginLength = (int) (startWidth * (1 - flag)) / 2;
invalidate();
}
});
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
drawCircle = false;
invalidate();
}
@Override
public void onAnimationEnd(Animator animation) {
setClickable(true);
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
animator.start();
}
//开始按钮的旋转动画,表示加载中
private void startCircleAnim() {
RotateAnimation ra = new RotateAnimation(0, 360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
ra.setDuration(duration * 2);
ra.setInterpolator(new LinearInterpolator());
ra.setRepeatCount(-1);
ra.setFillAfter(true);
startAnimation(ra);
}
public interface Loading {
//添加我们自己的逻辑
void startLoading();
}
}
调用
如果要使用揭露动画展示下一个界面的话,可以把该按钮在屏幕中的位置信息传递过去,以此作为目标界面揭露动画的起点。
<lxf.widget.LoginButton
android:layout_width="match_parent"
android:layout_height="45dp"
android:layout_marginTop="20dp"
android:onClick="login"
app:bgColor="@color/btn_normal"
app:bgColorFocused="@color/btn_focused"
app:text="登 陆"
app:textColor="@color/white"
app:textSize="18sp" />
public void login(final View view) {
LoginButton lb = (LoginButton) view;
final int[] location = new int[2];
lb.getLocationInWindow(location);
location[0] = location[0] + lb.getWidth() / 2;
location[1] = location[1] + lb.getHeight() / 2;
lb.setLoading(new LoginButton.Loading() {
@Override
public void startLoading() {
loading(view, location);
}
});
lb.click();
}
private void loading(final View view, final int[] location) {
Observable.timer(1000, TimeUnit.MILLISECONDS)
.subscribe(new Observer<Long>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Long value) {
Intent intent = new Intent(view.getContext(), LoginTargetActivity.class);
intent.putExtra("location", location);
view.getContext().startActivity(intent);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
((Activity) view.getContext()).overridePendingTransition(0, 0);
}
}
@Override
public void onError(Throwable e) {
Toast.makeText(getApplicationContext(),"登录失败",Toast.LENGTH_SHORT).show();
((LoginButton) view).reset();
}
@Override
public void onComplete() {
finish();
}
});
}
使用揭露动画展示第二个页面
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login_target);
WindowManager wm = (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics outMetrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(outMetrics);
screenW = outMetrics.widthPixels;
screenH = outMetrics.heightPixels;
rootView = (LinearLayout) findViewById(R.id.rootView);
rootView.post(new Runnable() {
@Override
public void run() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
int[] location = getIntent().getIntArrayExtra("location");
Animator animator = ViewAnimationUtils.createCircularReveal(rootView, location[0], location[1], 0,
(float) Math.hypot(Math.max(location[0], screenW - location[0]), Math.max(location[1], screenH - location[1])));
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
//// TODO: 业务逻辑
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
animator.setDuration(500).start();
}
}
});
}