Android自定义控件之仿京东商城下拉刷新
2016-07-07 12:39
295 查看
转载请注明出处http://blog.csdn.net/nugongahou110
直接上图先!
![](https://img-blog.csdn.net/20151123202428833)
这个下拉刷新效果分为两个部分:
step1:快递小哥和快递包裹的缩放效果,看上去就像是快递小哥跑过来一手拿过快递的样子
step2:快递小哥拿到包裹后,开启暴走模式!玩命送快递
PS:不得不赞一下京东的快递,真的很快!
好了马屁拍完了,我们先来看一看第一部分的效果是怎么实现的。首先快递小哥和包裹是两张图片
![](https://img-blog.csdn.net/20151123203322250)
![](https://img-blog.csdn.net/20151123203333853)
我们看到快递小哥是从小变大的,快递包裹也是从小变大的,所以我们要自定义一个控件,就起名为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(0);
}
/**
* 测量方法
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
}
//测量宽度
private int measureWidth(int widthMeasureSpec){
int result = 0;
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 = 0;
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()*10/27, scaledPeople.getHeight()/5, 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()/2 , measuredHeight/2);
mPaint.setAlpha(mCurrentAlpha);
canvas.drawBitmap(scaledGoods, measuredWidth-scaledGoods.getWidth(), measuredHeight/2-scaledGoods.getHeight()/2, mPaint);
canvas.restore();
canvas.save();
canvas.scale(mCurrentProgress, mCurrentProgress , 0 , measuredHeight/2);
mPaint.setAlpha(mCurrentAlpha);
canvas.drawBitmap(scaledPeople, 0,0,mPaint);
canvas.restore();
}
/**
* 根据进度来对小哥和包裹进行缩放
* @param currentProgress
*/
public void setCurrentProgress(float currentProgress){
this.mCurrentProgress = currentProgress;
this.mCurrentAlpha = (int) (currentProgress*255);
}
}还是老方法,我们写完这个效果,就先来测试一下吧,还是用一个SeekBar来模拟一个进度值
![](https://img-blog.csdn.net/20151123204339451)
好了,第一部分已经完成,接下来我们看第二部分,第二部分就简单了,就是一个帧动画,一共有3张图片
![](https://img-blog.csdn.net/20151123204623192)
![](https://img-blog.csdn.net/20151123204633197)
![](https://img-blog.csdn.net/20151123204642802)
我们在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 = 0;
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 = 0;
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上下载源码
直接上图先!
分析
这个下拉刷新效果分为两个部分: 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(0);
}
/**
* 测量方法
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
}
//测量宽度
private int measureWidth(int widthMeasureSpec){
int result = 0;
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 = 0;
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()*10/27, scaledPeople.getHeight()/5, 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()/2 , measuredHeight/2);
mPaint.setAlpha(mCurrentAlpha);
canvas.drawBitmap(scaledGoods, measuredWidth-scaledGoods.getWidth(), measuredHeight/2-scaledGoods.getHeight()/2, mPaint);
canvas.restore();
canvas.save();
canvas.scale(mCurrentProgress, mCurrentProgress , 0 , measuredHeight/2);
mPaint.setAlpha(mCurrentAlpha);
canvas.drawBitmap(scaledPeople, 0,0,mPaint);
canvas.restore();
}
/**
* 根据进度来对小哥和包裹进行缩放
* @param currentProgress
*/
public void setCurrentProgress(float currentProgress){
this.mCurrentProgress = currentProgress;
this.mCurrentAlpha = (int) (currentProgress*255);
}
}还是老方法,我们写完这个效果,就先来测试一下吧,还是用一个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 = 0;
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 = 0;
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上下载源码
相关文章推荐
- Android事件分发机制解析
- Android Scroller OverScroller使用
- Android 自定义控件 轻松实现360软件详情页
- 一种常规Android脱壳技术的拓展(附工具)
- KJFrameForAndroid框架分析
- android 四大组件Acitivity (2) 保存activity状态 Fragment
- Android应用程序通用自动脱壳方法研究
- Android ADT修改主题配色方案 Color theme
- Release from App standby(Android M)
- 定时执行alarm(Android M)
- Android分享SDK 从零开始自主研发
- 如何修改frameworks实现音量统一调节
- Androidn Notification的使用,解决找不到setLatestEventInfo方法
- Android下so注入汇总
- No Crypto provider (Android N)
- Android 自定义View (四) 视频音量调控
- Android 自定义View (三) 圆环交替 等待效果
- Android模拟器连接localhost错误解决办法
- Android 自定义View (二) 进阶
- 自定义只有上下边界的边框背景