編輯:關於Android編程
美團的下拉刷新分為三個狀態:
第一個狀態為下拉刷新狀態(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 = DONE;
//根據狀態改變headerView,主要是更新動畫和文字等信息
changeHeaderByState(state);
}
}
//如果當前狀態為下拉刷新並且已經記錄y坐標
if (state == PULL_TO_REFRESH && isRecord) {
setSelection(0);
//如果下拉距離大於等於headerView的總高度
if (-headerViewHeight+offsetY/RATIO>=0) {
//將狀態變為放開刷新
state = RELEASE_TO_REFRESH;
//根據狀態改變headerView,主要是更新動畫和文字等信息
changeHeaderByState(state);
//如果當前y的位移值小於0,即為headerView隱藏了
}else if (offsetY<=0) {
//將狀態變為done
state = DONE;
//根據狀態改變headerView,主要是更新動畫和文字等信息
changeHeaderByState(state);
}
}
//如果當前狀態為done並且已經記錄y坐標
if (state == DONE && isRecord) {
//如果位移值大於0
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);
}
}
public class MainActivity extends Activity implements OnMeiTuanRefreshListener{
private MeiTuanListView mListView;
private List mDatas;
private ArrayAdapter mAdapter;
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();
}
}
在Activity中含有EditText時,我們常常在AndroidManifest.xml中為該Activity設置android:windowSoftInputMod
nexus7一鍵刷安卓android L教程,谷歌之子,大家都知道他吧。現在小編給大家帶來了nexus7一鍵刷安卓android l的方法,目前的Andro
安卓v7支持包下的ListView替代品————RecyclerViewRecyclerView這個控件也出來很久了,相信
0. 介紹此文將對Github上lguipeng大神所開發的 極簡筆記 v2.0 (點我下載源碼)代碼進行分析學習。通過此文你將學到:應用源碼的研讀方法 MVP架構模式