git 下载地址:https://github.com/nugongshou110/MeiTuanRefreshListView
实现原理:
美团的下拉刷新分为三个状态: 第一个状态为下拉刷新状态(pull to refresh),在这个状态下是一个绿色的椭圆随着下拉的距离动态改变其大小。 第二个部分为放开刷新状态(release to refresh),在这个状态下是一个帧动画,效果为从躺着变为站起来的动画。 第三个部分为刷新状态(refreshing),在这个状态下也是一个帧动画,是摇头的动画。
第一个状态的实现: 我们的思路是:当前这个椭圆形有一个进度值,这个进度值从0变为1,然后对这个椭圆形进行缩放,我们可以使用自定义View来实现这个效果,我们先来用一个SeekBar来模仿一下下拉距离的进度 我们解压美团apk后拿到这张图片:
public class MeiTuanRefreshFirstStepView extends View{
private Bitmap initialBitmap;
private int measuredWidth;
private int measuredHeight;
private Bitmap endBitmap;
private float mCurrentProgress;
private Bitmap scaledBitmap;
public MeiTuanRefreshFirstStepView(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
init(context);
}
public MeiTuanRefreshFirstStepView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public MeiTuanRefreshFirstStepView(Context context) {
super(context);
init(context);
}
private void init(Context context) {
//这个就是那个椭圆形图片
initialBitmap = Bitmap.createBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.pull_image));
//这个是第二个状态娃娃的图片,之所以要这张图片,是因为第二个状态和第三个状态的图片的大小是一致的,而第一阶段
//椭圆形图片的大小与第二阶段和第三阶段不一致,因此我们需要根据这张图片来决定第一张图片的宽高,来保证
//第一阶段和第二、三阶段的View的宽高一致
endBitmap = Bitmap.createBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.pull_end_image_frame_05));
}
/**
* 重写onMeasure方法主要是设置wrap_content时 View的大小
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//根据设置的宽度来计算高度 设置为符合第二阶段娃娃图片的宽高比例
setMeasuredDimension(measureWidth(widthMeasureSpec),measureWidth(widthMeasureSpec)*endBitmap.getHeight()/endBitmap.getWidth());
}
/**
* 当wrap_content的时候,宽度即为第二阶段娃娃图片的宽度
* @param widMeasureSpec
* @return
*/
private int measureWidth(int widMeasureSpec){
int result = 0;
int size = MeasureSpec.getSize(widMeasureSpec);
int mode = MeasureSpec.getMode(widMeasureSpec);
if (mode == MeasureSpec.EXACTLY){
result = size;
}else{
result = endBitmap.getWidth();
if (mode == MeasureSpec.AT_MOST){
result = Math.min(result,size);
}
}
return result;
}
/**
* 在onLayout里面获得测量后View的宽高
* @param changed
* @param left
* @param top
* @param right
* @param bottom
*/
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
super.onLayout(changed, left, top, right, bottom);
measuredWidth = getMeasuredWidth();
measuredHeight = getMeasuredHeight();
//根据第二阶段娃娃宽高 给椭圆形图片进行等比例的缩放
scaledBitmap = Bitmap.createScaledBitmap(initialBitmap, measuredWidth,measuredWidth*initialBitmap.getHeight()/initialBitmap.getWidth(), true);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//这个方法是对画布进行缩放,从而达到椭圆形图片的缩放,第一个参数为宽度缩放比例,第二个参数为高度缩放比例,
canvas.scale(mCurrentProgress, mCurrentProgress, measuredWidth/2, measuredHeight/2);
//将等比例缩放后的椭圆形画在画布上面
canvas.drawBitmap(scaledBitmap,0,measuredHeight/4,null);
}
/**
* 设置缩放比例,从0到1 0为最小 1为最大
* @param currentProgress
*/
public void setCurrentProgress(float currentProgress){
mCurrentProgress = currentProgress;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
然后在Activity里面:
/**
* Created by zhangqi on 15/11/1.
*/
public class MyActivity extends Activity {
private MeiTuanRefreshFirstStepView mFirstView;
private SeekBar mSeekBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
mSeekBar = (SeekBar) findViewById(R.id.seekbar);
mFirstView = (MeiTuanRefreshFirstStepView) findViewById(R.id.first_view);
mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
//计算出当前seekBar滑动的比例结果为0到1
float currentProgress = (float) i / (float) seekBar.getMax();
//给我们的view设置当前进度值
mFirstView.setCurrentProgress(currentProgress);
//重画
mFirstView.postInvalidate();
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
}
}
第二个状态的实现:
第二个状态是一个帧动画,我们为了保证View大小的统一,我们也进行自定义View,这个自定义View很简单,只是为了和第一阶段View的宽高保证一致即可
public class MeiTuanRefreshSecondStepView extends View{
private Bitmap endBitmap;
public MeiTuanRefreshSecondStepView(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
init();
}
public MeiTuanRefreshSecondStepView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MeiTuanRefreshSecondStepView(Context context) {
super(context);
init();
}
private void init() {
endBitmap = Bitmap.createBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.pull_end_image_frame_05));
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureWidth(widthMeasureSpec), measureWidth(widthMeasureSpec)*endBitmap.getHeight()/endBitmap.getWidth());
}
private int measureWidth(int widthMeasureSpec){
int result = 0;
int size = MeasureSpec.getSize(widthMeasureSpec);
int mode = MeasureSpec.getMode(widthMeasureSpec);
if (mode == MeasureSpec.EXACTLY) {
result = size;
}else {
result = endBitmap.getWidth();
if (mode == MeasureSpec.AT_MOST) {
result = Math.min(result, size);
}
}
return result;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
我们用xml定义一组帧动画
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
帧动画的启动和停止方式:
mSecondView = (MeiTuanRefreshSecondStepView) headerView.findViewById(R.id.second_view);
mSecondView.setBackgroundResource(R.drawable.pull_to_refresh_second_anim);
secondAnim = (AnimationDrawable) mSecondView.getBackground();
//启动
secondAnim.start();
//停止
secondAnim.stop();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
和第二个状态同理,我们也通过自定义View来确保三个状态的View的宽高保持一致
public class MeiTuanRefreshThirdStepView extends View{
private Bitmap endBitmap;
public MeiTuanRefreshThirdStepView(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
init();
}
public MeiTuanRefreshThirdStepView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MeiTuanRefreshThirdStepView(Context context) {
super(context);
init();
}
private void init() {
endBitmap = Bitmap.createBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.pull_end_image_frame_05));
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureWidth(widthMeasureSpec), measureWidth(widthMeasureSpec)*endBitmap.getHeight()/endBitmap.getWidth());
}
private int measureWidth(int widthMeasureSpec){
int result = 0;
int size = MeasureSpec.getSize(widthMeasureSpec);
int mode = MeasureSpec.getMode(widthMeasureSpec);
if (mode == MeasureSpec.EXACTLY) {
result = size;
}else {
result = endBitmap.getWidth();
if (mode == MeasureSpec.AT_MOST) {
result = Math.min(result, size);
}
}
return result;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
我们在xml中定义一组帧动画:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
帧动画的启动和停止方式和第二个状态的一样
最后代码请到文章开头去下载