前面寫了4篇自定義控件的部落格,并且開通了一個專欄,把4篇文章添加到專欄中了,耐心等待部落格專欄的徽章出現,奈何等了幾周後還是沒有出現,後來發現至少需要5篇文章才能出現專欄徽章,于是有了這篇仿我大京東快遞小哥的下拉重新整理。
直接上圖先!
分析
這個下拉重新整理效果分為兩個部分:
step1:快遞小哥和快遞包裹的縮放效果,看上去就像是快遞小哥跑過來一手拿過快遞的樣子
step2:快遞小哥拿到包裹後,開啟暴走模式!玩命送快遞
PS:不得不贊一下京東的快遞,真的很快!
step1
好了馬屁拍完了,我們先來看一看第一部分的效果是怎麼實作的。首先快遞小哥和包裹是兩張圖檔
我們看到快遞小哥是從小變大的,快遞包裹也是從小變大的,是以我們要自定義一個控件,就起名為FirstStepView.java吧
public class FirstSetpView extends View{
private Bitmap goods;
private Bitmap people;
private Bitmap peopleWithGoods;
private int measuredWidth;
private int measuredHeight;
private float mCurrentProgress;
private int mCurrentAlpha;
private Paint mPaint;
private Bitmap scaledPeople;
private Bitmap scaledGoods;
public FirstSetpView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public FirstSetpView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public FirstSetpView(Context context) {
super(context);
init();
}
private void init(){
//包裹bitmap
goods = BitmapFactory.decodeResource(getResources(), R.mipmap.app_refresh_goods_0);
//快遞小哥bitmap
people = BitmapFactory.decodeResource(getResources(), R.mipmap.app_refresh_people_0);
//這是後面動畫中的最後一張圖檔,拿這張圖檔的作用是用它的寬高來測量
//我們這個自定義View的寬高
peopleWithGoods = BitmapFactory.decodeResource(getResources(), R.mipmap.app_refresh_people_3);
//來個畫筆,我們注意到快遞小哥和包裹都有一個漸變效果的,我們用
//mPaint.setAlpha來實作這個漸變的效果
mPaint = new Paint();
//首先設定為完全透明
mPaint.setAlpha();
}
/**
* 測量方法
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
}
//測量寬度
private int measureWidth(int widthMeasureSpec){
int result = ;
int size = MeasureSpec.getSize(widthMeasureSpec);
int mode = MeasureSpec.getMode(widthMeasureSpec);
if (MeasureSpec.EXACTLY == mode) {
result = size;
}else {
result = peopleWithGoods.getWidth();
if (MeasureSpec.AT_MOST == mode) {
result = Math.min(result, size);
}
}
return result;
}
//測量高度
private int measureHeight(int heightMeasureSpec){
int result = ;
int size = MeasureSpec.getSize(heightMeasureSpec);
int mode = MeasureSpec.getMode(heightMeasureSpec);
if (MeasureSpec.EXACTLY == mode) {
result = size;
}else {
result = peopleWithGoods.getHeight();
if (MeasureSpec.AT_MOST == mode) {
result = Math.min(result, size);
}
}
return result;
}
//在這裡面拿到測量後的寬和高,w就是測量後的寬,h是測量後的高
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
measuredWidth = w;
measuredHeight = h;
//根據測量後的寬高來對快遞小哥做一個縮放
scaledPeople = Bitmap.createScaledBitmap(people,measuredWidth,measuredHeight,true);
//根據測量後的寬高來對快遞包裹做一個縮放
scaledGoods = Bitmap.createScaledBitmap(goods, scaledPeople.getWidth()*/, scaledPeople.getHeight()/, true);
}
/**
* 繪制方法
* @param canvas
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//由于包裹和快遞小哥要分别來畫,是以使用save和restore方法
//save
//畫包裹
//restore
//save
//畫小哥
//restore
canvas.save();
canvas.scale(mCurrentProgress, mCurrentProgress , measuredWidth-scaledGoods.getWidth()/ , measuredHeight/);
mPaint.setAlpha(mCurrentAlpha);
canvas.drawBitmap(scaledGoods, measuredWidth-scaledGoods.getWidth(), measuredHeight/-scaledGoods.getHeight()/, mPaint);
canvas.restore();
canvas.save();
canvas.scale(mCurrentProgress, mCurrentProgress , , measuredHeight/);
mPaint.setAlpha(mCurrentAlpha);
canvas.drawBitmap(scaledPeople, ,,mPaint);
canvas.restore();
}
/**
* 根據進度來對小哥和包裹進行縮放
* @param currentProgress
*/
public void setCurrentProgress(float currentProgress){
this.mCurrentProgress = currentProgress;
this.mCurrentAlpha = (int) (currentProgress*);
}
}
還是老方法,我們寫完這個效果,就先來測試一下吧,還是用一個SeekBar來模拟一個進度值
public class MainActivity extends Activity {
private SeekBar sb;
private FirstSetpView mFirstStepView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sb = (SeekBar) findViewById(R.id.seekbar);
mFirstStepView = (FirstSetpView) findViewById(R.id.firstview);
sb.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
float currentProgress = (float)seekBar.getProgress()/(float)seekBar.getMax();
mFirstStepView.setCurrentProgress(currentProgress);
mFirstStepView.invalidate();
}
});
}
}
step2
好了,第一部分已經完成,接下來我們看第二部分,第二部分就簡單了,就是一個幀動畫,一共有3張圖檔
我們在res/drawable/下寫一個幀動畫second_step_animation.xml
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false"
>
<item android:drawable="@mipmap/app_refresh_people_1" android:duration="70"/>
<item android:drawable="@mipmap/app_refresh_people_2" android:duration="70"/>
<item android:drawable="@mipmap/app_refresh_people_3" android:duration="70"/>
</animation-list>
我們為了保證第一階段和第二階段的View的寬和高一緻,我們還要再自定義一個View,不過我們不用去畫什麼,隻需要測量一下寬和高,讓他和FirstSetpView的寬高保持一緻
public class SecondStepView extends View{
private Bitmap endBitmap;
public SecondStepView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public SecondStepView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public SecondStepView(Context context) {
super(context);
init();
}
private void init(){
//拿到幀動畫第三章圖檔,我們的FirstStepView的寬高也是根據這張圖檔來測量的,是以我們就能
//保證兩個View的寬高一緻了
endBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.app_refresh_people_3);
}
/**
* 隻需要測量方法
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
}
private int measureWidth(int widthMeasureSpec){
int result = ;
int size = MeasureSpec.getSize(widthMeasureSpec);
int mode = MeasureSpec.getMode(widthMeasureSpec);
if (MeasureSpec.EXACTLY == mode) {
result = size;
}else {
result = endBitmap.getWidth();
if (MeasureSpec.AT_MOST == mode) {
result = Math.min(size, result);
}
}
return result;
}
private int measureHeight(int heightMeasureSpec){
int result = ;
int size = MeasureSpec.getSize(heightMeasureSpec);
int mode = MeasureSpec.getMode(heightMeasureSpec);
if (MeasureSpec.EXACTLY == mode) {
result = size;
}else {
result = endBitmap.getHeight();
if (MeasureSpec.AT_MOST == mode) {
result = Math.min(size, result);
}
}
return result;
}
}
好了,接下來再複習一下怎麼執行幀動畫
secondStepView = (SecondStepView) headerView.findViewById(R.id.second_step_view);
secondStepView.setBackgroundResource(R.drawable.second_step_animation);
secondAnimation = (AnimationDrawable) secondStepView.getBackground();
secondAnimation是一個AnimationDrawable對象,我們可以調用他的start方法開始幀動畫,調用stop結束幀動畫
secondAnimation.start();
secondAnimation.stop();
下拉重新整理的實作
這一部分我在Android自定義控件之仿美團下拉重新整理 中已經詳細分析過了,是以這裡就不再贅述了。
完整代碼
歡迎大家上我的GitHub上下載下傳源碼