編輯:關於Android編程
昨天偶偶然看見UI 給的一個交互的效果,原圖如下
就是下面的loginbutton,於是大概模仿了一下,
並沒有做這個UI的全部效果,有興趣的可以完善後面展開的效果<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPs/Cw+bKx2RlbW+1xGJ1dHRvbtCnufs8YnIgLz4NCjxpbWcgYWx0PQ=="這裡寫圖片描述" src="/uploadfile/Collfiles/20160716/201607160921281027.gif" title="\" />
這個View用到的知識點比較簡單:
view的坐標系知識,(大家沒有不熟悉的吧) view的canvas基本API(畫矩形,畫扇形,) view的自定義屬性(attr提供選項) 屬性動畫的知識(老生常談的知識,ObjectAnimation和ValueAniamtion)下面我們就一步步實現這個button
我們寫一個自定義的類繼承View實現其構造,在構造函數中獲取自定義屬性的值
public ATLoginButton(Context context) {
this(context, null);
}
public ATLoginButton(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ATLoginButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 獲取自定義屬性集合
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ATLoginButton, defStyleAttr, R.style.def_button_style);
int indexCount = typedArray.getIndexCount();
for (int i = 0; i < indexCount; i++) {
int attr = typedArray.getIndex(i);
switch (attr) {
case R.styleable.ATLoginButton_button_color:
buttonColor = typedArray.getColor(attr, getResources().getColor(R.color.colorAccent));
break;
case R.styleable.ATLoginButton_text_color:
textColor = typedArray.getColor(attr, getResources().getColor(R.color.colorW));
break;
case R.styleable.ATLoginButton_text_size:
textSize = typedArray.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 12, getResources().getDisplayMetrics()));
break;
case R.styleable.ATLoginButton_login_text:
loginDesc = typedArray.getString(attr);
break;
case R.styleable.ATLoginButton_failed_text:
failDesc = typedArray.getString(attr);
break;
case R.styleable.ATLoginButton_circle_loading_color:
circlerLoadingColor = typedArray.getColor(attr, Color.GRAY);
break;
case R.styleable.ATLoginButton_circle_loading_width:
circleLoadingLineWidth = typedArray.getDimensionPixelOffset(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2, getResources().getDisplayMetrics()));
break;
case R.styleable.ATLoginButton_failed_button_color:
failedButtonColor = typedArray.getColor(attr, Color.GRAY);
break;
}
}
typedArray.recycle();
init();
}
重寫view的onMeasue,確定和測量我們view的大小和測試模式的確定
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
// 主要view支持wrap_content屬性,如果不處理,warpcontent和matchparent感官給我們的感覺是一樣的,其實並不然,想了解的可以看下官方文檔
int widthSize;
int heightSize;
if (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED) {
widthSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DE_W, getResources().getDisplayMetrics());
widthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);
}
if (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) {
heightSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DE_H, getResources().getDisplayMetrics());
heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
然後獲取測量後view的寬和高
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
circleAndRoundSize = h / 2;
textPoint = getTextPointInView(loginDesc);
buttonRectF = new RectF(0, 0, mWidth, mHeight);
mText = loginDesc;
viewState = LoginViewState.NORMAL_STATE;
}
然後就是最後一步了onDraw,幾分鐘,我們已經完成了百分之80的工作
最後20%就是讓view的內容畫到畫布上,並且讓其動起來就ok了
畫圓形的button,注意這個圓角button,動起來的時候量個半圓需要合並成一個完整的圈,所以倒角的半徑就已經確定了,就是我們view高度的一半,這裡需要注意下
//畫button代碼
private void drawButton(Canvas canvas) {
canvas.drawRoundRect(buttonRectF, circleAndRoundSize, circleAndRoundSize, buttonPaint);
}
畫button上面的文字
private void drawTextDesc(Canvas canvas, String textDesc) {
canvas.drawText(textDesc, textPoint.x, textPoint.y, textPaint);
}
小插曲,我們在繪制文字的時候為了讓文字居中,我們需要獲取文字測量後的信息如下
// 這裡我直接獲取了文字的寬高然後把文字在view中的坐標信息計算並返回出去了
private Point getTextPointInView(String textDesc) {
Point point = new Point();
int textW = (mWidth - (int) textPaint.measureText(textDesc)) / 2;
Paint.FontMetrics fm = textPaint.getFontMetrics();
int textH = (int) Math.ceil(fm.descent - fm.top);
point.set(textW, (mHeight + textH) / 2);
return point;
}
畫扇形的方法,這個方形就是我們那個loading的圓圈
private void drawCircleLoading(Canvas canvas) {
float circleSpacing = circleAndRoundSize / 4;
float x = (mHeight - 10) / 2;
float y = (mHeight - 10) / 2;
canvas.translate(mWidth / 2, y);
canvas.scale(1F, 1F);
canvas.rotate(0);
RectF rectF = new RectF(-x + circleSpacing, -y + circleSpacing, x - circleSpacing, y - circleSpacing);
canvas.drawArc(rectF, -45, 270, false, circleLoadingPaint);
}
ok到現在我們所有的圖形元素都准備到位,剩下的就是提供兩個方法,一個是開始登陸,button變成圓形,還有一個就是登陸的結果不管失敗還是成功都要變成button,以及還有一個在變成圓球的時候旋轉的動畫
一步步來
public void buttonLoginAction() {
setClickable(false);
buttonPaint.setColor(buttonColor);
if (viewState != LoginViewState.NORMAL_STATE) {
circleLoadingPaint.setColor(circlerLoadingColor);
}
ValueAnimator valueAnimator = getValA(0F, 1F);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
// 這裡是我們根據valueani返回的比例,確定button的新的left和right
Float aFloat = Float.valueOf(valueAnimator.getAnimatedValue().toString());
int left = (int) ((aFloat) * mWidth);
int jL = mWidth / 2 - circleAndRoundSize;
if (left >= jL) {
//由於float不好做比價所以轉成int,如果新的left坐標大於view的測量一半說明這個時候應該變成圓形了,
// 我們手動讓其變成正規圓,拋棄float帶來的誤差
buttonRectF = new RectF(jL, 0, jL + mHeight, mHeight);
textPaint.setColor(Color.TRANSPARENT);
isLoading = true;
// 動畫取消
invalidate();
valueAnimator.cancel();
startLoading();
viewState = LoginViewState.LOADING_STATE;
return;
}
float right = (1 - aFloat) * mWidth;
buttonRectF = new RectF(left, 0, right, mHeight);
invalidate();
}
});
valueAnimator.start();
}
然後就是類似的一個方法,圓圈變成button的方法
public void buttonLoaginResultAciton(final boolean isSuccess) {
viewState = isSuccess ? LoginViewState.SUCCESS_STATE : LoginViewState.FAILED_STATE;
stopLoading();
ValueAnimator valueAnimator = getValA(0F, 1F);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
//依然是計算新的left
int jL = mWidth / 2 - circleAndRoundSize;
Float aFloat = Float.valueOf(valueAnimator.getAnimatedValue().toString());
int left = (int) ((1 - aFloat) * jL);
float right = jL + mHeight + jL * aFloat;
buttonRectF = new RectF(left, 0, right, mHeight);
textPaint.setColor(textColor);
if (isSuccess){
// 登陸成功,重置view的狀態
mText = loginDesc;
textPoint = getTextPointInView(mText);
buttonPaint.setColor(buttonColor);
invalidate();
if (aFloat.intValue() == 1) {
setClickable(true);
buttonRectF = new RectF(0, 0, mWidth, mHeight);
invalidate();
valueAnimator.cancel();
}
}else {
// 登陸失敗,進入顫抖動畫顯示失敗的文字和背景
mText = failDesc;
textPoint = getTextPointInView(mText);
buttonPaint.setColor(failedButtonColor);
invalidate();
if (aFloat.intValue() == 1) {
setClickable(true);
buttonRectF = new RectF(0, 0, mWidth, mHeight);
invalidate();
shakeFailed();
valueAnimator.cancel();
}
}
}
});
valueAnimator.start();
}
這樣我們view的全部工作都做完了,剩下的就是在Mainactivity裡面用一下
private void addListener2Button(final ATLoginButton atLoginButton, final boolean loaginStatus) {
atLoginButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// 這裡是登陸動畫,然後去請求服務器接口
atLoginButton.buttonLoginAction();
// 加入三秒後,登陸失敗或者成功
atLoginButton.postDelayed(new Runnable() {
@Override
public void run() {
//這裡調用View獲取登陸狀態的方法,成功或者失敗
atLoginButton.buttonLoaginResultAciton(loaginStatus);
String notice = loaginStatus ? "登陸成功,重置button狀態" : "登錄失敗,顯示失敗狀態";
Toast.makeText(getApplicationContext(), notice, Toast.LENGTH_SHORT).show();
}
}, 3000);
}
});
}
由於 就一個這個demo就一個自定義view,項目就不上傳了,把完整的代碼給大家,有興趣的可以放到AS裡面跑一下,謝謝!
最後給大家推薦我的一個比價完整的開源項目,SoHOT鏈接如下,
文章末尾有Githup免費下載地址,希望star謝謝
public class ATLoginButton extends View {
private static final float DE_W = 280.F;
private static final float DE_H = 65.F;
private static final long ANIMATION_TIME = 800;
private int buttonColor;
private int textColor;
private int textSize;
private int circlerLoadingColor;
private int failedButtonColor;
private int circleLoadingLineWidth;
private String loginDesc;
private String failDesc;
private String mText;
private Paint buttonPaint;
private Paint textPaint;
private Paint circleLoadingPaint;
private int mHeight;
private int mWidth;
private int circleAndRoundSize;
private RectF buttonRectF;
private Point textPoint;
private boolean isLoading;
private RotateAnimation rotateAnimation;
private int viewState;
public ATLoginButton(Context context) {
this(context, null);
}
public ATLoginButton(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ATLoginButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ATLoginButton, defStyleAttr, R.style.def_button_style);
int indexCount = typedArray.getIndexCount();
for (int i = 0; i < indexCount; i++) {
int attr = typedArray.getIndex(i);
switch (attr) {
case R.styleable.ATLoginButton_button_color:
buttonColor = typedArray.getColor(attr, getResources().getColor(R.color.colorAccent));
break;
case R.styleable.ATLoginButton_text_color:
textColor = typedArray.getColor(attr, getResources().getColor(R.color.colorW));
break;
case R.styleable.ATLoginButton_text_size:
textSize = typedArray.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 12, getResources().getDisplayMetrics()));
break;
case R.styleable.ATLoginButton_login_text:
loginDesc = typedArray.getString(attr);
break;
case R.styleable.ATLoginButton_failed_text:
failDesc = typedArray.getString(attr);
break;
case R.styleable.ATLoginButton_circle_loading_color:
circlerLoadingColor = typedArray.getColor(attr, Color.GRAY);
break;
case R.styleable.ATLoginButton_circle_loading_width:
circleLoadingLineWidth = typedArray.getDimensionPixelOffset(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2, getResources().getDisplayMetrics()));
break;
case R.styleable.ATLoginButton_failed_button_color:
failedButtonColor = typedArray.getColor(attr, Color.GRAY);
break;
}
}
typedArray.recycle();
init();
}
private void init() {
buttonPaint = creatPaint(buttonColor, 0, Paint.Style.FILL, circleLoadingLineWidth);
circleLoadingPaint = creatPaint(circlerLoadingColor, 0, Paint.Style.STROKE, circleLoadingLineWidth);
textPaint = creatPaint(textColor, textSize, Paint.Style.FILL, circleLoadingLineWidth);
}
private Paint creatPaint(int paintColor, int textSize, Paint.Style style, int lineWidth) {
Paint paint = new Paint();
paint.setColor(paintColor);
paint.setAntiAlias(true);
paint.setStrokeWidth(lineWidth);
paint.setDither(true);
paint.setTextSize(textSize);
paint.setStyle(style);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStrokeJoin(Paint.Join.ROUND);
return paint;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize;
int heightSize;
if (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED) {
widthSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DE_W, getResources().getDisplayMetrics());
widthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);
}
if (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) {
heightSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DE_H, getResources().getDisplayMetrics());
heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
circleAndRoundSize = h / 2;
textPoint = getTextPointInView(loginDesc);
buttonRectF = new RectF(0, 0, mWidth, mHeight);
mText = loginDesc;
viewState = LoginViewState.NORMAL_STATE;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawButton(canvas);
drawTextDesc(canvas, mText);
if (isLoading) {
drawCircleLoading(canvas);
}
}
private void drawCircleLoading(Canvas canvas) {
float circleSpacing = circleAndRoundSize / 4;
float x = (mHeight - 10) / 2;
float y = (mHeight - 10) / 2;
canvas.translate(mWidth / 2, y);
canvas.scale(1F, 1F);
canvas.rotate(0);
RectF rectF = new RectF(-x + circleSpacing, -y + circleSpacing, x - circleSpacing, y - circleSpacing);
canvas.drawArc(rectF, -45, 270, false, circleLoadingPaint);
}
private void drawTextDesc(Canvas canvas, String textDesc) {
canvas.drawText(textDesc, textPoint.x, textPoint.y, textPaint);
}
private void drawButton(Canvas canvas) {
canvas.drawRoundRect(buttonRectF, circleAndRoundSize, circleAndRoundSize, buttonPaint);
}
private Point getTextPointInView(String textDesc) {
Point point = new Point();
int textW = (mWidth - (int) textPaint.measureText(textDesc)) / 2;
Paint.FontMetrics fm = textPaint.getFontMetrics();
int textH = (int) Math.ceil(fm.descent - fm.top);
point.set(textW, (mHeight + textH) / 2);
return point;
}
public void buttonLoginAction() {
setClickable(false);
buttonPaint.setColor(buttonColor);
if (viewState != LoginViewState.NORMAL_STATE) {
circleLoadingPaint.setColor(circlerLoadingColor);
}
ValueAnimator valueAnimator = getValA(0F, 1F);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
Float aFloat = Float.valueOf(valueAnimator.getAnimatedValue().toString());
int left = (int) ((aFloat) * mWidth);
int jL = mWidth / 2 - circleAndRoundSize;
if (left >= jL) {
buttonRectF = new RectF(jL, 0, jL + mHeight, mHeight);
textPaint.setColor(Color.TRANSPARENT);
isLoading = true;
invalidate();
valueAnimator.cancel();
startLoading();
viewState = LoginViewState.LOADING_STATE;
return;
}
float right = (1 - aFloat) * mWidth;
buttonRectF = new RectF(left, 0, right, mHeight);
invalidate();
}
});
valueAnimator.start();
}
public void buttonLoaginResultAciton(final boolean isSuccess) {
viewState = isSuccess ? LoginViewState.SUCCESS_STATE : LoginViewState.FAILED_STATE;
stopLoading();
ValueAnimator valueAnimator = getValA(0F, 1F);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
int jL = mWidth / 2 - circleAndRoundSize;
Float aFloat = Float.valueOf(valueAnimator.getAnimatedValue().toString());
int left = (int) ((1 - aFloat) * jL);
float right = jL + mHeight + jL * aFloat;
buttonRectF = new RectF(left, 0, right, mHeight);
textPaint.setColor(textColor);
if (isSuccess){
mText = loginDesc;
textPoint = getTextPointInView(mText);
buttonPaint.setColor(buttonColor);
invalidate();
if (aFloat.intValue() == 1) {
setClickable(true);
buttonRectF = new RectF(0, 0, mWidth, mHeight);
invalidate();
valueAnimator.cancel();
}
}else {
mText = failDesc;
textPoint = getTextPointInView(mText);
buttonPaint.setColor(failedButtonColor);
invalidate();
if (aFloat.intValue() == 1) {
setClickable(true);
buttonRectF = new RectF(0, 0, mWidth, mHeight);
invalidate();
shakeFailed();
valueAnimator.cancel();
}
}
}
});
valueAnimator.start();
}
private void stopLoading() {
isLoading = false;
circleLoadingPaint.setColor(Color.TRANSPARENT);
if (null != rotateAnimation) {
rotateAnimation.cancel();
}
}
private void shakeFailed() {
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(this, "translationX", 0, 10);
objectAnimator.setDuration(500);
objectAnimator.setInterpolator(new CycleInterpolator(10));
objectAnimator.start();
}
private void startLoading() {
rotateAnimation = new RotateAnimation(0F, 360F, mWidth / 2, mHeight / 2);
rotateAnimation.setDuration(500);
rotateAnimation.setRepeatCount(Animation.INFINITE);
rotateAnimation.setInterpolator(new LinearInterpolator());
startAnimation(rotateAnimation);
}
private static class LoginViewState {
static final int NORMAL_STATE = 90;
static final int LOADING_STATE = 91;
static final int FAILED_STATE = 92;
static final int SUCCESS_STATE = 93;
}
}
項目裡面用到了語音喚醒功能,前面一直在用訊飛的語音識別,本來打算也是直接用訊飛的語音喚醒,但是訊飛的語音喚醒要收費,試用版只有35天有效期。只好改用百度語音,百度語音所有
最近下了個攜程App,點開首頁看,注意到其按鈕在點擊的時候並不是我們經常看到的變色效果,而是先收縮,放開時,再回到原來的大小,感覺這個效果雖然小,但是感覺非常新穎,於是決
android退出應用程序會調用android.os.Process.killProcess(android.os.Process.myPid())或是System.ex
本文分享自己在視頻錄制播放過程中遇到的一些問題,主要包括: 視頻錄制流程 視頻預覽及SurfaceHolder 視頻清晰度及文件大小 視頻文件旋轉 一、視頻錄制