編輯:關於Android編程
晚上好,現在是凌晨兩點半,然後我還在寫代碼。電腦裡播放著《凌晨兩點半》,晚上寫代碼,腦子更清醒,思路更清晰。
今天聊聊屬性動畫和自定義View搭配使用,前面都講到自定義View和屬性動畫,但是一起用的還是不多,剛巧今晚手機提示我更新系統,我看到那個更新的動畫還不錯,仔細的分析了一下,於是我也決定寫一個,不是一模一樣的,但是效果和原理是一樣的。
先看看圖:
vc/7yqehozwvcD4NCjxwPsv50tTE0bXj0rLKx9Ta1eK49rKowMvP38nPoaPV4rj2sqjAy8/fwODLxtPa0ru49suusqjOxqOs0rLT0LrctuC1xLTzyfHQtLn9y66yqM7GtcTQp7n7o6zL+dLUtbHO0sPH1qq1wNXiuPbKx8qyw7S1xMqxuvKjrL7Nsci9z8jd0tfKtc/W1eK49rmmxNzBy6GjPC9wPg0KPHA+1eLSu7TOo6zPyL+0v7TTw7W9wcvKssO0udi8/LXEvLzK9aGjPC9wPg0KPHA+ytfPyNKqvenJ3LXEo6y+zcrHuN/W0LXEyrG68tGnuf21xNPgz9K6r8r9o6y7ubzHtcOx7bTvyr3C8KO/PGJyIC8+DQp5ID0gQXNpbih3eCtiKStoPGJyIC8+DQrV4rj2uavKvcDvo7p307DP7NbcxtqjrEHTsM/s1fG3+aOsaNOwz+x5zrvWw6OsYs6qs/XP4KO7PC9wPg0KPHA+vdPPwsC0vs2/tL+01eLSu7TO08O1vcHLxMTQqbHkwb+jujwvcD4NCjxwcmUgY2xhc3M9"brush:java;">
// y = Asin(wx+b)+h
private static final float STRETCH_FACTOR_A = 50;
private static final int OFFSET_Y = 0;
// 第一條水波移動速度
private static final int TRANSLATE_X_SPEED_ONE = 7;
// 第二條水波移動速度
private static final int TRANSLATE_X_SPEED_TWO = 5;
// 第三條水波移動速度
private static final int TRANSLATE_X_SPEED_Three = 10;
private float mCycleFactorW;
private int mTotalWidth, mTotalHeight;
private float[] mYPositions;
private float[] mResetOneYPositions;
private float[] mResetTwoYPositions;
private float[] mResetThreeYPositions;
private int mXOffsetSpeedOne;
private int mXOffsetSpeedTwo;
private int mXOffsetSpeedThree;
private int mXOneOffset;
private int mXTwoOffset;
private int mXThreeOffset;
private int height = 1200;
private Paint mWavePaint, mWavePaint2,mWavePaint3;
private DrawFilter mDrawFilter;
都是常規的,像畫筆、高寬等等。
然後看看初始化的操作,在構造函數裡面:
public WaterRipple(Context context, AttributeSet attrs) {
super(context, attrs);
// 將dp轉化為px,用於控制不同分辨率上移動速度基本一致
mXOffsetSpeedOne = DensityUtil.dip2px(context, TRANSLATE_X_SPEED_ONE);
mXOffsetSpeedTwo = DensityUtil.dip2px(context, TRANSLATE_X_SPEED_TWO);
mXOffsetSpeedThree = DensityUtil.dip2px(context, TRANSLATE_X_SPEED_Three);
// 初始繪制波紋的畫筆
mWavePaint = new Paint();
// 去除畫筆鋸齒
mWavePaint.setAntiAlias(true);
// 設置風格為實線
mWavePaint.setStyle(Style.FILL);
// 設置畫筆顏色
mWavePaint.setColor(Color.parseColor("#ffffff"));
mDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG
| Paint.FILTER_BITMAP_FLAG);
// 初始繪制波紋的畫筆
mWavePaint2 = new Paint();
// 去除畫筆鋸齒
mWavePaint2.setAntiAlias(true);
// 設置風格為實線
mWavePaint2.setStyle(Style.FILL);
// 設置畫筆顏色
mWavePaint2.setColor(Color.parseColor("#eeeeee"));
// 初始繪制波紋的畫筆
mWavePaint3 = new Paint();
// 去除畫筆鋸齒
mWavePaint3.setAntiAlias(true);
// 設置風格為實線
mWavePaint3.setStyle(Style.FILL);
// 設置畫筆顏色
mWavePaint3.setColor(Color.parseColor("#ffffff"));
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// 記錄下view的寬高
mTotalWidth = w;
mTotalHeight = h;
// 用於保存原始波紋的y值
mYPositions = new float[mTotalWidth];
// 用於保存波紋一的y值
mResetOneYPositions = new float[mTotalWidth];
// 用於保存波紋二的y值
mResetTwoYPositions = new float[mTotalWidth];
// 用於保存波紋三的y值
mResetThreeYPositions = new float[mTotalWidth];
// 將周期定為view總寬度
mCycleFactorW = (float) (2 * Math.PI / mTotalWidth);
// 根據view總寬度得出所有對應的y值
for (int i = 0; i < mTotalWidth; i++) {
mYPositions[i] = (float) (STRETCH_FACTOR_A
* Math.sin(mCycleFactorW * i) + OFFSET_Y);
}
}
這個初始化倒是不用怎麼解釋,重點說一下下面的方法:
private void resetPositonY() {
// mXOneOffset代表當前第一條水波紋要移動的距離
int yOneInterval = mYPositions.length - mXOneOffset;
// 使用System.arraycopy方式重新填充第一條波紋的數據
// 第一個參數就是源數組,第二個參數是要復制的源數組中的起始位置,
// 第三個參數是目標數組,第四個參數是要復制到的目標數組的起始位置,第五個參數是要復制的元素的長度
System.arraycopy(mYPositions, mXOneOffset, mResetOneYPositions, 0,
yOneInterval);
System.arraycopy(mYPositions, 0, mResetOneYPositions, yOneInterval,
mXOneOffset);
int yTwoInterval = mYPositions.length - mXTwoOffset;
System.arraycopy(mYPositions, mXTwoOffset, mResetTwoYPositions, 0,
yTwoInterval);
System.arraycopy(mYPositions, 0, mResetTwoYPositions, yTwoInterval,
mXTwoOffset);
int yThreeInterval = mYPositions.length - mXThreeOffset;
System.arraycopy(mYPositions, mXThreeOffset, mResetThreeYPositions, 0,
yThreeInterval);
System.arraycopy(mYPositions, 0, mResetThreeYPositions, yThreeInterval,
mXThreeOffset);
}
這裡面用到了一個System.arraycopy的方法,這個在我們平時的時候可能用得比較少,但是確實非常好用,建議大家可以看看這個的具體用法。我這裡也是盡量的寫滿注釋,以便更好的理解。
接下來就是最關鍵的onDraw方法,代碼如下:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 從canvas層面去除繪制時鋸齒
canvas.setDrawFilter(mDrawFilter);
resetPositonY();
for (int i = 0; i < mTotalWidth; i++) {
// 繪制第一條水波紋
canvas.drawLine(i, mTotalHeight - mResetOneYPositions[i] - height,
i, mTotalHeight, mWavePaint);
// 繪制第二條水波紋
canvas.drawLine(i, mTotalHeight - mResetTwoYPositions[i] - height
+ 200, i, mTotalHeight, mWavePaint2);
// 繪制第三條水波紋
canvas.drawLine(i, mTotalHeight - mResetThreeYPositions[i] - height
+ 400, i, mTotalHeight, mWavePaint3);
}
// 改變兩條波紋的移動點
mXOneOffset += mXOffsetSpeedOne;
mXTwoOffset += mXOffsetSpeedTwo;
mXThreeOffset += mXOffsetSpeedThree;
// 如果已經移動到結尾處,則重新記錄
if (mXOneOffset >= mTotalWidth) {
mXOneOffset = 0;
}
if (mXTwoOffset > mTotalWidth) {
mXTwoOffset = 0;
}
if (mXThreeOffset > mTotalWidth) {
mXThreeOffset = 0;
}
}
我這裡繪制了三個波浪線,以及處理了一些在繪制中的數據。
最後暴露給外面一個方法,用於動態的調整高度。
public void setHeight(int height) {
this.height = height;
postInvalidate();
}
最重要的View已經繪制出來了,下面看看其他的代碼:
首先是布局文件,activity_main.xml
這一次的布局寫的比較多,也是用到了多層的嵌套。所以在閱讀的時候要很注意細節。
接下來是MainActivity.java:
public class MainActivity extends Activity {
private int height2 = 1200;
private int size = 0;
private ProgressBar bar;
private int height;
private WaterRipple ripple;
private TextView text_state;
private LinearLayout layout1, layout2;
private RelativeLayout layout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
ripple = (WaterRipple) findViewById(R.id.hehe);
bar = (ProgressBar) findViewById(R.id.progressBar1);
layout1 = (LinearLayout) findViewById(R.id.content_linear);
layout2 = (LinearLayout) findViewById(R.id.progress_linear);
text_state = (TextView) findViewById(R.id.text_state);
layout = (RelativeLayout) findViewById(R.id.relativi);
WindowManager wm = this.getWindowManager();
height = wm.getDefaultDisplay().getHeight();
height2 = (int)(height*0.7);
ripple.setHeight(height2);
bar.setMax(81);
}
Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case 1:
if (size < 81) {
bar.setProgress(size);
text_state.setText("下載進度:"
+ (float) (((float) (size) / 81) * 100) + "%");
size++;
} else {
startAnim();
layout2.setVisibility(View.GONE);
}
break;
case 2:
ripple.setHeight(height2);
ViewGroup.LayoutParams params = layout.getLayoutParams();
params.height = height - height2;
layout.setLayoutParams(params);
height2++;
break;
case 3:
ObjectAnimator animator = ObjectAnimator.ofFloat(ripple,
"alpha", 1f, 0f,0.5f);
ObjectAnimator animator2 = ObjectAnimator.ofFloat(layout,
"alpha", 1f, 0f,0.5f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(animator, animator2);
animatorSet.setDuration(300);
animatorSet.start();
break;
default:
break;
}
};
};
public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
while (size < 81) {
SystemClock.sleep(100);
handler.sendEmptyMessage(1);
}
}
}).start();
layout1.setVisibility(View.GONE);
layout2.setVisibility(View.VISIBLE);
}
public void startAnim() {
new Thread(new Runnable() {
public void run() {
while (height2 < height) {
SystemClock.sleep(100);
handler.sendEmptyMessage(2);
}
handler.sendEmptyMessage(3);
}
}).start();
}
}
這裡我有兩個線程,主要是控制下載的進度展示和水波紋效果。具體的參數,使用者可以根據自己的需要,再作調整。
這裡面也用了一個工具類DensityUtil.java:
public class DensityUtil {
/**
* 根據手機的分辨率從 dp 的單位 轉成為 px(像素)
*/
public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
/**
* 根據手機的分辨率從 px(像素) 的單位 轉成為 dp
*/
public static int px2dip(Context context, float pxValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
}
}
還有那個控件WaterRipple.java:
public class WaterRipple extends View {
// y = Asin(wx+b)+h
private static final float STRETCH_FACTOR_A = 50;
private static final int OFFSET_Y = 0;
// 第一條水波移動速度
private static final int TRANSLATE_X_SPEED_ONE = 7;
// 第二條水波移動速度
private static final int TRANSLATE_X_SPEED_TWO = 5;
// 第三條水波移動速度
private static final int TRANSLATE_X_SPEED_Three = 10;
private float mCycleFactorW;
private int mTotalWidth, mTotalHeight;
private float[] mYPositions;
private float[] mResetOneYPositions;
private float[] mResetTwoYPositions;
private float[] mResetThreeYPositions;
private int mXOffsetSpeedOne;
private int mXOffsetSpeedTwo;
private int mXOffsetSpeedThree;
private int mXOneOffset;
private int mXTwoOffset;
private int mXThreeOffset;
private int height = 1200;
private Paint mWavePaint, mWavePaint2,mWavePaint3;
private DrawFilter mDrawFilter;
public WaterRipple(Context context, AttributeSet attrs) {
super(context, attrs);
// 將dp轉化為px,用於控制不同分辨率上移動速度基本一致
mXOffsetSpeedOne = DensityUtil.dip2px(context, TRANSLATE_X_SPEED_ONE);
mXOffsetSpeedTwo = DensityUtil.dip2px(context, TRANSLATE_X_SPEED_TWO);
mXOffsetSpeedThree = DensityUtil.dip2px(context, TRANSLATE_X_SPEED_Three);
// 初始繪制波紋的畫筆
mWavePaint = new Paint();
// 去除畫筆鋸齒
mWavePaint.setAntiAlias(true);
// 設置風格為實線
mWavePaint.setStyle(Style.FILL);
// 設置畫筆顏色
mWavePaint.setColor(Color.parseColor("#ffffff"));
mDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG
| Paint.FILTER_BITMAP_FLAG);
// 初始繪制波紋的畫筆
mWavePaint2 = new Paint();
// 去除畫筆鋸齒
mWavePaint2.setAntiAlias(true);
// 設置風格為實線
mWavePaint2.setStyle(Style.FILL);
// 設置畫筆顏色
mWavePaint2.setColor(Color.parseColor("#eeeeee"));
// 初始繪制波紋的畫筆
mWavePaint3 = new Paint();
// 去除畫筆鋸齒
mWavePaint3.setAntiAlias(true);
// 設置風格為實線
mWavePaint3.setStyle(Style.FILL);
// 設置畫筆顏色
mWavePaint3.setColor(Color.parseColor("#ffffff"));
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 從canvas層面去除繪制時鋸齒
canvas.setDrawFilter(mDrawFilter);
resetPositonY();
for (int i = 0; i < mTotalWidth; i++) {
// 減400只是為了控制波紋繪制的y的在屏幕的位置,大家可以改成一個變量,然後動態改變這個變量,從而形成波紋上升下降效果
// 繪制第一條水波紋
canvas.drawLine(i, mTotalHeight - mResetOneYPositions[i] - height,
i, mTotalHeight, mWavePaint);
// 繪制第二條水波紋
canvas.drawLine(i, mTotalHeight - mResetTwoYPositions[i] - height
+ 200, i, mTotalHeight, mWavePaint2);
// 繪制第三條水波紋
canvas.drawLine(i, mTotalHeight - mResetThreeYPositions[i] - height
+ 400, i, mTotalHeight, mWavePaint3);
}
// 改變兩條波紋的移動點
mXOneOffset += mXOffsetSpeedOne;
mXTwoOffset += mXOffsetSpeedTwo;
mXThreeOffset += mXOffsetSpeedThree;
// 如果已經移動到結尾處,則重頭記錄
if (mXOneOffset >= mTotalWidth) {
mXOneOffset = 0;
}
if (mXTwoOffset > mTotalWidth) {
mXTwoOffset = 0;
}
if (mXThreeOffset > mTotalWidth) {
mXThreeOffset = 0;
}
// 引發view重繪,一般可以考慮延遲20-30ms重繪,空出時間片
}
private void resetPositonY() {
// mXOneOffset代表當前第一條水波紋要移動的距離
int yOneInterval = mYPositions.length - mXOneOffset;
// 使用System.arraycopy方式重新填充第一條波紋的數據
// 第一個參數就是源數組,第二個參數是要復制的源數組中的起始位置,
// 第三個參數是目標數組,第四個參數是要復制到的目標數組的起始位置,第五個參數是要復制的元素的長度
System.arraycopy(mYPositions, mXOneOffset, mResetOneYPositions, 0,
yOneInterval);
System.arraycopy(mYPositions, 0, mResetOneYPositions, yOneInterval,
mXOneOffset);
int yTwoInterval = mYPositions.length - mXTwoOffset;
System.arraycopy(mYPositions, mXTwoOffset, mResetTwoYPositions, 0,
yTwoInterval);
System.arraycopy(mYPositions, 0, mResetTwoYPositions, yTwoInterval,
mXTwoOffset);
int yThreeInterval = mYPositions.length - mXThreeOffset;
System.arraycopy(mYPositions, mXThreeOffset, mResetThreeYPositions, 0,
yThreeInterval);
System.arraycopy(mYPositions, 0, mResetThreeYPositions, yThreeInterval,
mXThreeOffset);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// 記錄下view的寬高
mTotalWidth = w;
mTotalHeight = h;
// 用於保存原始波紋的y值
mYPositions = new float[mTotalWidth];
// 用於保存波紋一的y值
mResetOneYPositions = new float[mTotalWidth];
// 用於保存波紋二的y值
mResetTwoYPositions = new float[mTotalWidth];
// 用於保存波紋三的y值
mResetThreeYPositions = new float[mTotalWidth];
// 將周期定為view總寬度
mCycleFactorW = (float) (2 * Math.PI / mTotalWidth);
// 根據view總寬度得出所有對應的y值
for (int i = 0; i < mTotalWidth; i++) {
mYPositions[i] = (float) (STRETCH_FACTOR_A
* Math.sin(mCycleFactorW * i) + OFFSET_Y);
}
}
public void setHeight(int height) {
this.height = height;
postInvalidate();
}
}
以上就是全部內容,有個bug,就是在轉換的時候部分手機會出現卡頓,後面我加上插值器調整一下,先睡了。如果代碼有什麼不妥的地方歡迎指出。
項目地址:YingBeautyNote簡介:一款類似印象筆記的 App,隨時記錄您的生活點滴。一款類似印象筆記的 App,隨時記錄您的生活點滴,但時目前功能還沒達到印象筆
上一篇文章Android 中的 Service 全面總結詳解【下】 介紹了Service的一些知識以及本地Service的使用,如果對Service還不太了解的建議先看下
Android實現帶圖標的ListView已經成為每一個android應用開發初學者的必學知識,熟練掌握此知識點,對我們以後的開發工作會大有好處,今天我們就來一步一步實現
本文會實現一個類似網易新聞(不說網易新聞大家可能不知道大概是什麼樣子)點擊超多選項卡,選項卡動態滑動的效果。首先來看看布局,就是用HorizontalScro