Android自界说控件之仿美团下拉改写-Android-优质IT资源分享社区

admin
管理员
管理员
  • UID1
  • 粉丝30
  • 关注4
  • 发帖数581
  • 社区居民
  • 忠实会员
  • 原创写手
阅读:244回复:0

  Android自界说控件之仿美团下拉改写

楼主#
更多 发布于:2016-05-20 19:04

美团的下拉改写分为三个状况:

榜首个状况为下拉改写状况(pull to refresh),在这个状况下是一个绿色的椭圆随着下拉的间隔动态改动其巨细。
第二个部分为铺开改写状况(release to refresh),在这个状况下是一个帧动画,作用为从躺着变为站起来的动画。
第三个部分为改写状况(refreshing),在这个状况下也是一个帧动画,是摇头的动画。
其中第二和第三个状况很简单,即是两个帧动画,榜首个状况咱们能够用自界说View来完成。
榜首个状况的完成:
咱们的思路是:当时这个椭圆形有一个进展值,这个进展值从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;    }
然后在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;
   }}
咱们用xml界说一组帧动画
帧动画的发动和中止办法:
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();
第三个状况的完成:
和第二个状况同理,咱们也经过自界说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;
   }
咱们在xml中界说一组帧动画:
帧动画的发动和中止办法和第二个状况的相同
下拉改写的完成:
首要咱们要界说好几个状况,下拉改写有这么几个状况:
DONE:躲藏的状况
PULL_TO_REFRESH:下拉改写的状况
RELEASE_TO_REFRESH:松开改写的状况
REFRESHING:正在改写的状况
/** * Created by zhangqi on 15/10/18. */public class MeiTuanListView
extends ListView implements AbsListView.OnScrollListener{    private static
final int DONE = 0;    private static final int PULL_TO_REFRESH = 1;    private
static final int RELEASE_TO_REFRESH = 2;    private static final int REFRESHING
= 3;    private static final int RATIO = 3;    private LinearLayout headerView;
   private int headerViewHeight;    private float startY;    private float
offsetY;    private TextView tv_pull_to_refresh;    private
OnMeiTuanRefreshListener mOnRefreshListener;    private int state;    private
int mFirstVisibleItem;    private boolean isRecord;    private boolean isEnd;  
 private boolean isRefreable;    private FrameLayout mAnimContainer;    private
Animation animation;    private SimpleDateFormat format;    private
MeiTuanRefreshFirstStepView mFirstView;    private MeiTuanRefreshSecondStepView
mSecondView;    private AnimationDrawable secondAnim;    private
MeiTuanRefreshThirdStepView mThirdView;    private AnimationDrawable thirdAnim;
   public MeiTuanListView(Context context) {        super(context);      
 init(context);    }    public MeiTuanListView(Context context, AttributeSet
attrs) {        super(context, attrs);        init(context);    }    public
MeiTuanListView(Context context, AttributeSet attrs, int defStyleAttr) {      
 super(context, attrs, defStyleAttr);        init(context);    }    public
interface OnMeiTuanRefreshListener{        void onRefresh();    }    /**     *
回调接口,想完成下拉改写的listview完成此接口     * @param onRefreshListener     */    public void
setOnMeiTuanRefreshListener(OnMeiTuanRefreshListener onRefreshListener){      
 mOnRefreshListener = onRefreshListener;        isRefreable = true;    }    /**
    * 改写结束,从主线程发送过来,而且改动headerView的状况和文字动画信息     */    public void
setOnRefreshComplete(){        //必定要将isEnd设置为true,以便于下次的下拉改写        isEnd =
true;        state = DONE;        changeHeaderByState(state);    }    private
void init(Context context) {        setOverScrollMode(View.OVER_SCROLL_NEVER);  
     setOnScrollListener(this);        headerView = (LinearLayout)
LayoutInflater.from(context).inflate(R.layout.meituan_item, null, false);      
 mFirstView = (MeiTuanRefreshFirstStepView)
headerView.findViewById(R.id.first_view);        tv_pull_to_refresh = (TextView)
headerView.findViewById(R.id.tv_pull_to_refresh);        mSecondView =
(MeiTuanRefreshSecondStepView) headerView.findViewById(R.id.second_view);      
 mSecondView.setBackgroundResource(R.drawable.pull_to_refresh_second_anim);    
   secondAnim = (AnimationDrawable) mSecondView.getBackground();      
 mThirdView = (MeiTuanRefreshThirdStepView)
headerView.findViewById(R.id.third_view);      
 mThirdView.setBackgroundResource(R.drawable.pull_to_refresh_third_anim);      
 thirdAnim = (AnimationDrawable) mThirdView.getBackground();      
 measureView(headerView);        addHeaderView(headerView);      
 headerViewHeight = headerView.getMeasuredHeight();      
 headerView.setPadding(0, -headerViewHeight, 0, 0);      
 Log.i("zhangqi","headerViewHeight="+headerViewHeight);        state = DONE;    
   isEnd = true;        isRefreable = false;    }    @Override    public void
onScrollStateChanged(AbsListView absListView, int i) {    }    @Override  
 public void onScroll(AbsListView absListView, int firstVisibleItem, int
visibleItemCount, int totalItemCount) {        mFirstVisibleItem =
firstVisibleItem;    }    @Override    public boolean onTouchEvent(MotionEvent
ev) {        if (isEnd) {//假如如今时结束的状况,即改写结束了,能够再次改写了,在onRefreshComplete中设置      
     if (isRefreable) {//假如如今是可改写状况   在setOnMeiTuanListener中设置为true            
   switch (ev.getAction()){                    //用户按下                case
MotionEvent.ACTION_DOWN:                    //假如当时是在listview顶部而且没有记载y坐标        
           if (mFirstVisibleItem == 0 && !isRecord) {                  
     //将isRecord置为true,阐明如今已记载y坐标                        isRecord = true;      
                 //将当时y坐标赋值给startY开始y坐标                        startY =
ev.getY();                    }                    break;                //用户滑动
               case MotionEvent.ACTION_MOVE:                  
 //再次得到y坐标,用来和startY相减来核算offsetY位移值                    float tempY = ev.getY();
                   //复兴判别一下是不是为listview顶部而且没有记载y坐标                    if
(mFirstVisibleItem == 0 && !isRecord) {                        isRecord
= true;                        startY = tempY;                    }            
       //假如当时状况不是正在改写的状况,而且现已记载了y坐标                    if (state!=REFRESHING
&& isRecord ) {                        //核算y的偏移量                      
 offsetY = tempY - startY;                        //核算当时滑动的高度                  
     float currentHeight = (-headerViewHeight+offsetY/3);                      
 //用当时滑动的高度和头部headerView的总高度进行比 核算出当时滑动的百分比 0到1                        float
currentProgress = 1+currentHeight/headerViewHeight;                      
 //假如当时百分比大于1了,将其设置为1,意图是让榜首个状况的椭圆不再持续变大                        if
(currentProgress>=1) {                            currentProgress = 1;      
                 }                        //假如当时的状况是铺开改写,而且现已记载y坐标              
         if (state == RELEASE_TO_REFRESH && isRecord) {                
           setSelection(0);                          
 //假如当时滑动的间隔小于headerView的总高度                            if
(-headerViewHeight+offsetY/RATIO<0) {                              
 //将状况置为下拉改写状况                                state = PULL_TO_REFRESH;          
                     //依据状况改动headerView,主要是更新动画和文字等信息                          
     changeHeaderByState(state);                              
 //假如当时y的位移值小于0,即为headerView躲藏了                            }else if
(offsetY<=0) done="" state="=" if="" pull_to_refresh="" ratio="">=0) {    
                           //将状况变为铺开改写                                state =
RELEASE_TO_REFRESH;                              
 //依据状况改动headerView,主要是更新动画和文字等信息                              
 changeHeaderByState(state);                              
 //假如当时y的位移值小于0,即为headerView躲藏了                            }else if
(offsetY<=0) 0="" done="" state="=" if="" offsety="">=0) {                
               //将状况改为下拉改写状况                                state =
PULL_TO_REFRESH;                            }                        }          
             //假如为下拉改写状况                        if (state == PULL_TO_REFRESH) {
                           //则改动headerView的padding来完成下拉的作用                      
     headerView.setPadding(0,(int)(-headerViewHeight+offsetY/RATIO) ,0,0);      
                     //给榜首个状况的View设置当时进展值                          
 mFirstView.setCurrentProgress(currentProgress);                            //重画
                           mFirstView.postInvalidate();                        }
                       //假如为铺开改写状况                        if (state ==
RELEASE_TO_REFRESH) {                            //改动headerView的padding值        
                  
 headerView.setPadding(0,(int)(-headerViewHeight+offsetY/RATIO) ,0, 0);        
                   //给榜首个状况的View设置当时进展值                          
 mFirstView.setCurrentProgress(currentProgress);                            //重画
                           mFirstView.postInvalidate();                        }
                   }                    break;                //当用户手指抬起时        
       case MotionEvent.ACTION_UP:                    //假如当时状况为下拉改写状况          
         if (state == PULL_TO_REFRESH) {                      
 //滑润的躲藏headerView                      
 this.smoothScrollBy((int)(-headerViewHeight+offsetY/RATIO)+headerViewHeight,
500);                        //依据状况改动headerView                      
 changeHeaderByState(state);                    }                  
 //假如当时状况为铺开改写                    if (state == RELEASE_TO_REFRESH) {            
           //滑润的滑到恰好显现headerView                      
 this.smoothScrollBy((int)(-headerViewHeight+offsetY/RATIO), 500);              
         //将当时状况设置为正在改写                        state = REFRESHING;              
         //回调接口的onRefresh办法                      
 mOnRefreshListener.onRefresh();                        //依据状况改动headerView      
                 changeHeaderByState(state);                    }              
     //这一套手势执行完,必定别忘了将记载y坐标的isRecord改为false,以便于下一次手势的执行                  
 isRecord = false;                    break;                }            }      
 }        return super.onTouchEvent(ev);    }    /**     *
依据状况改动headerView的动画和文字显现     * @param state     */    private void
changeHeaderByState(int state){        switch (state) {        case
DONE://假如的躲藏的状况            //设置headerView的padding为躲藏          
 headerView.setPadding(0, -headerViewHeight, 0, 0);            //榜首状况的view显现出来  
         mFirstView.setVisibility(View.VISIBLE);            //第二状况的view躲藏起来    
       mSecondView.setVisibility(View.GONE);            //中止第二状况的动画          
 secondAnim.stop();            //第三状况的view躲藏起来          
 mThirdView.setVisibility(View.GONE);            //中止第三状况的动画          
 thirdAnim.stop();            break;        case RELEASE_TO_REFRESH://当时状况为铺开改写
           //文字显现为铺开改写            tv_pull_to_refresh.setText("铺开改写");          
 //榜首状况view躲藏起来            mFirstView.setVisibility(View.GONE);          
 //第二状况view显现出来            mSecondView.setVisibility(View.VISIBLE);          
 //播映第二状况的动画            secondAnim.start();            //第三状况view躲藏起来          
 mThirdView.setVisibility(View.GONE);            //中止第三状况的动画          
 thirdAnim.stop();            break;        case PULL_TO_REFRESH://当时状况为下拉改写    
       //设置文字为下拉改写            tv_pull_to_refresh.setText("下拉改写");          
 //榜首状况view显现出来            mFirstView.setVisibility(View.VISIBLE);          
 //第二状况view躲藏起来            mSecondView.setVisibility(View.GONE);          
 //第二状况动画中止            secondAnim.stop();            //第三状况view躲藏起来          
 mThirdView.setVisibility(View.GONE);            //第三状况动画中止          
 thirdAnim.stop();            break;        case REFRESHING://当时状况为正在改写        
   //文字设置为正在改写            tv_pull_to_refresh.setText("正在改写");          
 //榜首状况view躲藏起来            mFirstView.setVisibility(View.GONE);          
 //第三状况view显现出来            mThirdView.setVisibility(View.VISIBLE);          
 //第二状况view躲藏起来            mSecondView.setVisibility(View.GONE);          
 //中止第二状况动画            secondAnim.stop();            //发动第三状况view          
 thirdAnim.start();            break;        default:            break;        }
   }    private void measureView(View child) {        ViewGroup.LayoutParams p =
child.getLayoutParams();        if (p == null) {            p = new
ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,                  
 ViewGroup.LayoutParams.WRAP_CONTENT);        }        int childWidthSpec =
ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width);        int lpHeight =
p.height;        int childHeightSpec;        if (lpHeight > 0) {          
 childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight,                  
 MeasureSpec.EXACTLY);        } else {            childHeightSpec =
MeasureSpec.makeMeasureSpec(0,                    MeasureSpec.UNSPECIFIED);    
   }        child.measure(childWidthSpec, childHeightSpec);    }}
全部准备就绪,在Activity中运用:
public class MainActivity extends Activity implements
OnMeiTuanRefreshListener{    private MeiTuanListView mListView;    private
ListmDatas;    private ArrayAdaptermAdapter;    private final static int
REFRESH_COMPLETE = 0;    /**     *
mHandler运行在主线程,由于setOnRefreshComplete需求改动ui,必须在主线程去改动ui     *
所以在handleMessage中调用mListView.setOnRefreshComplete();     */    private Handler
mHandler = new Handler(){        public void handleMessage(android.os.Message
msg) {            switch (msg.what) {            case REFRESH_COMPLETE:        
       mListView.setOnRefreshComplete();              
 mAdapter.notifyDataSetChanged();                mListView.setSelection(0);    
           break;            default:                break;            }      
 };    };    @Override    protected void onCreate(Bundle savedInstanceState) {  
     super.onCreate(savedInstanceState);      
 setContentView(R.layout.activity_main);        mListView = (MeiTuanListView)
findViewById(R.id.listview);        String[] data = new String[]{"hello
world","hello world","hello world","hello world",                "hello
world","hello world","hello world","hello world","hello world",              
 "hello world","hello world","hello world","hello world","hello world",};      
 mDatas = new ArrayList(Arrays.asList(data));        mAdapter = new
ArrayAdapter(this, android.R.layout.simple_list_item_1,mDatas);      
 mListView.setAdapter(mAdapter);      
 mListView.setOnMeiTuanRefreshListener(this);    }    @Override    public void
onRefresh() {        new Thread(new Runnable() {            @Override          
 public void run() {                try {                    Thread.sleep(3000);
                   mDatas.add(0, "new data");                  
 mHandler.sendEmptyMessage(REFRESH_COMPLETE);                } catch
(InterruptedException e) {                    // TODO Auto-generated catch block
                   e.printStackTrace();                }            }      
 }).start();    }}
完好代码
完好代码我们能够上我的GitHub下载,假如我们觉得还能够就star一下~哈哈








优质IT资源分享社区为你提供此文。
本站有大量优质android教程视频,资料等资源,包含android基础教程,高级进阶教程等等,教程视频资源涵盖传智播客,极客学院,达内,北大青鸟,猎豹网校等等IT职业培训机构的培训教学视频,价值巨大。欢迎点击下方链接查看。

android教程视频
优质IT资源分享社区(www.itziyuan.top)
一个免费,自由,开放,共享,平等,互助的优质IT资源分享网站。
专注免费分享各大IT培训机构最新培训教学视频,为你的IT学习助力!

!!!回帖受限制请看点击这里!!!
!!!资源失效请在此版块发帖说明!!!

[PS:按 CTRL+D收藏本站网址~]

——“优质IT资源分享社区”管理员专用签名~

本版相似帖子

游客