編輯:關於Android編程
項目中需要下拉刷新的功能,但是這個View不是ListView這類的控件,需要ViewGroup實現這個功能,一開始網上大略找了一下,沒發現特別合適的,代碼也是沒怎麼看懂,所以決定還是自己寫一個。
於是翻出XlistView的源碼看是一點一點看,再大致理解了XLisview源碼,終於決定自己動手啦
為了省事,headView還是用了XListView的HeadView,省了很多事:)
下拉刷新,下拉刷新,肯定是先實現下拉功能,最開始我是打算通過 extends ScrollView 來實現,因為有現成的滾動效果嘛,可是實際因為兩個原因放棄了:
1、ScrollView下只能有一個子控件View ,雖然在 Scroll下添加一個ViewGroup,然後講headView動態添加進前面的ViewGroup,但是我還是比較習慣studio的可視化預覽,總覺得不直觀!
2、 ScrollView內嵌ListView時會發生 沖突,還需要去重寫ListView。於是放棄換個思路!
關於上面的原因1:動態添加headView進ScrollView的中GroupView中,可以在重寫ScrollView的onViewAdded()方法,將初始化時解析的headView添加進子GroupView
@Override public void onViewAdded(View child) { super.onViewAdded(child); //因為headView要在最上面,最先想到的就是Vertical的LinearLayout LinearLayout linearLayout = (LinearLayout) getChildAt(0); linearLayout.addView(view, 0); }
換個思路,通過extends LinearLayout來實現吧!
先做准備工作,我們需要一個HeaderView以及要獲取到HeaderView的高度,還有初始時Layout的高度
private void initView(Context context) { mHeaderView = new SRefreshHeader(context); mHeaderViewContent = (RelativeLayout) mHeaderView.findViewById(R.id.slistview_header_content); setOrientation(VERTICAL); addView(mHeaderView, 0); getHeaderViewHeight(); getViewHeight(); }
mHeaderView = new SRefreshHeader(context);
通過構造方法實例化HeaderView
mHeaderViewContent = (RelativeLayout)
mHeaderView.findViewById(R.id.slistview_header_content);
這是解析headerView內容區域iew,等會兒要獲取這個view的高度,你肯定會問為啥不用上面的mHeaderView來獲取高度,點進構造方法裡可以看到如下代碼
// 初始情況,設置下拉刷新view高度為0 LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, 0); mContainer = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.listview_head_view_layout, null); w(mContainer, lp);
如果直接獲取mHeaderView的高度 那肯定是0
getHeaderViewHeight();
getViewHeight();
分別是獲取HeaderView的高度和Layout的初始高度
/** * 獲取headView高度 */ private void getHeaderViewHeight() { ViewTreeObserver vto2 = mHeaderViewContent.getViewTreeObserver(); vto2.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { mHeaderViewHeight = mHeaderViewContent.getHeight(); mHeaderViewContent.getViewTreeObserver().removeGlobalOnLayoutListener(this); } }); } /** * 獲取SRefreshLayout當前實例的高度 */ private void getViewHeight() { ViewTreeObserver thisView = getViewTreeObserver(); thisView.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { SRefreshLayout.this.mHeight = SRefreshLayout.this.getHeight(); SRefreshLayout.this.getViewTreeObserver().removeGlobalOnLayoutListener(this); } }); }
准備工作完成了,接下來就是要成下拉操作了
到這裡,肯定一下就想到了onTouchEvent()方法,是的!現在就開始在這裡施工
實現下拉一共 會經歷三個過程
ACTION_UP→ACTION_MOVE→ACTION_UP
在ACTION_UP事件中,也就是手指按下的時候,我們需要做的只是記錄按下時候的坐標
switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: //記錄起始高度 mLastY = ev.getRawY();//記錄按下時的Y坐標 break;
然後就是ACTION_MOVE事件了,這裡是最重要的,因為下拉時HeadView和Layout的高度變化都在這裡進行
case MotionEvent.ACTION_MOVE: if (!isRefreashing) isRefreashing = true; final float deltaY = ev.getRawY() - mLastY; mLastY = ev.getRawY(); updateHeaderViewHeight(deltaY / 1.8f);//按一定比例縮小移動距離 updateHeight(); break;
裡面的updateHeaderViewHeight和updateHeight分別是改變HeaderView的高度和Layout的高度
private void updateHeight() { ViewGroup.LayoutParams lp = getLayoutParams(); //更新當前layout實例高度為headerView高度加上最初的layout高度 //如果不更新layout 會造成內容高度壓縮 無法保持比例 lp.height = (mHeight + mHeaderView.getVisiableHeight()); setLayoutParams(lp); } private void updateHeaderViewHeight(float space) { // if (space < 0) // space = 0; // int factHeight = (int) (space - mHeaderViewHeight); if (mHeaderView.getStatus() != SRefreshHeader.STATE_REFRESHING) { //如果不處於刷新中同時如果高度 if (mHeaderView.getVisiableHeight() < mHeaderViewHeight * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_NORMAL) { mHeaderView.setState(SRefreshHeader.STATE_NORMAL); } if (mHeaderView.getVisiableHeight() > mHeaderViewHeight * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_READY) { mHeaderView.setState(SRefreshHeader.STATE_READY); } } mHeaderView.setVisiableHeight((int) space + mHeaderView.getVisiableHeight()); }
更新Header高度時,通過下拉的距離來判斷是否到達刷新的距離,上面代碼中我設定的是到達mHeaderView初始高度的兩倍,就進入“釋放刷新”的狀態,如果沒有達到則保持“下拉刷新”的狀態
HeaderView中的狀態一共設定了3個分別是
public final static int STATE_NORMAL = 0;//下拉刷新 public final static int STATE_READY = 1;//釋放刷新 public final static int STATE_REFRESHING = 2;//刷新中
更新高度的方法headerView和layout都是相同的,就是原高度加上移動的距離重新賦給headerView或者layout
mHeaderView.setVisiableHeight((int) space
+ mHeaderView.getVisiableHeight());
最後就是ACTION_UP事件了就是手指離開屏幕的時候,在這裡我們需要根據headerView目前狀態來決定headerView的最終狀態!
case MotionEvent.ACTION_UP: //松開時 //避免點擊事件觸發 if (!isRefreashing) break; //如果headView狀態處於READY狀態 則說明松開時應該進入REFRESHING狀態 if (mHeaderView.getStatus() == SRefreshHeader.STATE_READY) { mHeaderView.setState(SRefreshHeader.STATE_REFRESHING); } //根據狀態重置SrefreshLayout當前實例和headView高度 resetHeadView(mHeaderView.getStatus()); reset(mHeaderView.getStatus()); mLastY = -1;//重置坐標 break;
resetHeadView和reset分別是重置headerView高度和layout高度的方法
private void reset(int status) { ViewGroup.LayoutParams lp = getLayoutParams(); switch (status) { case SRefreshHeader.STATE_REFRESHING: lp.height = mHeight + mHeaderViewHeight; break; case SRefreshHeader.STATE_NORMAL: lp.height = mHeight; break; } setLayoutParams(lp); } private void resetHeadView(int status) { switch (status) { case SRefreshHeader.STATE_REFRESHING: mHeaderView.setVisiableHeight(mHeaderViewHeight); break; case SRefreshHeader.STATE_NORMAL: mHeaderView.setVisiableHeight(0); break; } }
實現方式也是一樣的。根據狀態來判斷,如果是處於刷新中,那headerView應該正常顯示,並且高度是初始的高度,如果處於NORMAL,也就是"下拉刷新"狀態,那麼說未觸發刷新,重置時,headerView應該被隱藏,也就是高度重置為0
到這裡下拉刷新操作也基本完成了,還需要加一個回調接口進行通知
interface OnRefreshListener { void onRefresh(); }
case MotionEvent.ACTION_UP: //松開時 //避免點擊事件觸發 if (!isRefreashing) break; //如果headView狀態處於READY狀態 則說明松開時應該進入REFRESHING狀態 if (mHeaderView.getStatus() == SRefreshHeader.STATE_READY) { mHeaderView.setState(SRefreshHeader.STATE_REFRESHING); if (mOnRefreshListener != null) mOnRefreshListener.onRefresh(); } //根據狀態重置SrefreshLayout當前實例和headView高度 resetHeadView(mHeaderView.getStatus()); reset(mHeaderView.getStatus()); mLastY = -1;//重置坐標 break;
好,到這裡就基本完成了,試試效果吧。咦,發現一個問題,嵌套ListView的時候為什麼這個Layout不能執行下拉刷新!仔細想想應該是事件分發的問題,還需要處理一下事件的攔截!
關於事件攔截的處理,閱讀了鴻洋大神寫的viewgroup事件分發的博客和Android-Ultra-Pull-To-Refresh的部分源碼,從中找到了解決辦法:
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { AbsListView absListView = null; for (int n = 0; n < getChildCount(); n++) { if (getChildAt(n) instanceof AbsListView) { absListView = (ListView) getChildAt(n); Logs.v("查找到listView"); } } if (absListView == null) return super.onInterceptTouchEvent(ev); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mStartY = ev.getRawY(); break; case MotionEvent.ACTION_MOVE: float space = ev.getRawY() - mStartY; Logs.v("space:" + space); if (space > 0 && !absListView.canScrollVertically(-1) && absListView.getFirstVisiblePosition() == 0) { Logs.v("攔截成功"); return true; } else { Logs.v("不攔截"); return false; } } return super.onInterceptTouchEvent(ev); }
其中
if (space > 0 && !absListView.canScrollVertically(-1) && absListView.getFirstVisiblePosition() == 0)
space即移動的距離 canScrollVertically()是判斷ListView能否在垂直方向上滾動,參數為負數時代表向上,為正數時代碼向下滾動,最後一個就是ListView第一個可見的item的postion
加上上面的事件攔截處理,一個可以滿足開頭提到的需求的Viewgroup也就完成了!
下面貼上Layout的源碼和HeaderView(直接使用的XlistView的HeaderView)的源碼
public class SRefreshLayout extends LinearLayout { private SRefreshHeader mHeaderView; private RelativeLayout mHeaderViewContent; private boolean isRefreashing; private float mLastY = -1;//按下的起始高度 private int mHeaderViewHeight;//headerView內容高度 private int mHeight;//布局高度 private float mStartY; interface OnRefreshListener { void onRefresh(); } public OnRefreshListener mOnRefreshListener; public SRefreshLayout(Context context) { super(context); initView(context); } public SRefreshLayout(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } public SRefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context); } private void initView(Context context) { mHeaderView = new SRefreshHeader(context); mHeaderViewContent = (RelativeLayout) mHeaderView.findViewById(R.id.slistview_header_content); setOrientation(VERTICAL); addView(mHeaderView, 0); getHeaderViewHeight(); getViewHeight(); } /** * 獲取headView高度 */ private void getHeaderViewHeight() { ViewTreeObserver vto2 = mHeaderViewContent.getViewTreeObserver(); vto2.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { mHeaderViewHeight = mHeaderViewContent.getHeight(); mHeaderViewContent.getViewTreeObserver().removeGlobalOnLayoutListener(this); } }); } /** * 獲取SRefreshLayout當前實例的高度 */ private void getViewHeight() { ViewTreeObserver thisView = getViewTreeObserver(); thisView.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { SRefreshLayout.this.mHeight = SRefreshLayout.this.getHeight(); SRefreshLayout.this.getViewTreeObserver().removeGlobalOnLayoutListener(this); } }); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { AbsListView absListView = null; for (int n = 0; n < getChildCount(); n++) { if (getChildAt(n) instanceof AbsListView) { absListView = (ListView) getChildAt(n); Logs.v("查找到listView"); } } if (absListView == null) return super.onInterceptTouchEvent(ev); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mStartY = ev.getRawY(); break; case MotionEvent.ACTION_MOVE: float space = ev.getRawY() - mStartY; Logs.v("space:" + space); if (space > 0 && !absListView.canScrollVertically(-1) && absListView.getFirstVisiblePosition() == 0) { Logs.v("攔截成功"); return true; } else { Logs.v("不攔截"); return false; } } return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent ev) { if (mLastY == -1) mLastY = ev.getRawY(); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: //記錄起始高度 mLastY = ev.getRawY();//記錄按下時的Y坐標 break; //手指離開屏幕時 case MotionEvent.ACTION_UP: //松開時 //避免點擊事件觸發 if (!isRefreashing) break; //如果headView狀態處於READY狀態 則說明松開時應該進入REFRESHING狀態 if (mHeaderView.getStatus() == SRefreshHeader.STATE_READY) { mHeaderView.setState(SRefreshHeader.STATE_REFRESHING); if (mOnRefreshListener != null) mOnRefreshListener.onRefresh(); } //根據狀態重置SrefreshLayout當前實例和headView高度 resetHeadView(mHeaderView.getStatus()); reset(mHeaderView.getStatus()); mLastY = -1;//重置坐標 break; case MotionEvent.ACTION_MOVE: if (!isRefreashing) isRefreashing = true; final float deltaY = ev.getRawY() - mLastY; mLastY = ev.getRawY(); updateHeaderViewHeight(deltaY / 1.8f);//按一定比例縮小移動距離 updateHeight(); break; } return super.onTouchEvent(ev); } private void reset(int status) { ViewGroup.LayoutParams lp = getLayoutParams(); switch (status) { case SRefreshHeader.STATE_REFRESHING: lp.height = mHeight + mHeaderViewHeight; break; case SRefreshHeader.STATE_NORMAL: lp.height = mHeight; break; } setLayoutParams(lp); } private void resetHeadView(int status) { switch (status) { case SRefreshHeader.STATE_REFRESHING: mHeaderView.setVisiableHeight(mHeaderViewHeight); break; case SRefreshHeader.STATE_NORMAL: mHeaderView.setVisiableHeight(0); break; } } private void updateHeight() { ViewGroup.LayoutParams lp = getLayoutParams(); //更新當前layout實例高度為headerView高度加上最初的layout高度 //如果不更新layout 會造成內容高度壓縮 無法保持比例 lp.height = (mHeight + mHeaderView.getVisiableHeight()); setLayoutParams(lp); } private void updateHeaderViewHeight(float space) { // if (space < 0) // space = 0; // int factHeight = (int) (space - mHeaderViewHeight); if (mHeaderView.getStatus() != SRefreshHeader.STATE_REFRESHING) { //如果不處於刷新中同時如果高度 if (mHeaderView.getVisiableHeight() < mHeaderViewHeight * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_NORMAL) { mHeaderView.setState(SRefreshHeader.STATE_NORMAL); } if (mHeaderView.getVisiableHeight() > mHeaderViewHeight * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_READY) { mHeaderView.setState(SRefreshHeader.STATE_READY); } } mHeaderView.setVisiableHeight((int) space + mHeaderView.getVisiableHeight()); } public void stopRefresh() { if (mHeaderView.getStatus() == SRefreshHeader.STATE_REFRESHING) { mHeaderView.setState(SRefreshHeader.STATE_NORMAL); resetHeadView(SRefreshHeader.STATE_NORMAL); reset(SRefreshHeader.STATE_NORMAL); } } public void setOnRefreshListener(OnRefreshListener onRefreshListener) { this.mOnRefreshListener = onRefreshListener; } }
public class SRefreshHeader extends LinearLayout { private LinearLayout mContainer; private int mState = STATE_NORMAL; private Animation mRotateUpAnim; private Animation mRotateDownAnim; private final int ROTATE_ANIM_DURATION = 500; public final static int STATE_NORMAL = 0;//下拉刷新 public final static int STATE_READY = 1;//釋放刷新 public final static int STATE_REFRESHING = 2;//刷新中 private ImageView mHeadArrowImage; private TextView mHeadLastRefreashTimeTxt; private TextView mHeadHintTxt; private TextView mHeadLastRefreashTxt; private ProgressBar mRefreshingProgress; public SRefreshHeader(Context context) { super(context); initView(context); } /** * @param context * @param attrs */ public SRefreshHeader(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } private void initView(Context context) { // 初始情況,設置下拉刷新view高度為0 LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, 0); mContainer = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.listview_head_view_layout, null); addView(mContainer, lp); setGravity(Gravity.BOTTOM); mHeadArrowImage = (ImageView) findViewById(R.id.slistview_header_arrow); mHeadLastRefreashTimeTxt = (TextView) findViewById(R.id.slistview_header_time); mHeadHintTxt = (TextView) findViewById(R.id.slistview_header_hint_text); mHeadLastRefreashTxt = (TextView) findViewById(R.id.slistview_header_last_refreash_txt); mRefreshingProgress = (ProgressBar) findViewById(R.id.slistview_header_progressbar); mRotateUpAnim = new RotateAnimation(0.0f, -180.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); mRotateUpAnim.setDuration(ROTATE_ANIM_DURATION); mRotateUpAnim.setFillAfter(true); mRotateDownAnim = new RotateAnimation(-180.0f, 0.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); mRotateDownAnim.setDuration(ROTATE_ANIM_DURATION); mRotateDownAnim.setFillAfter(true); } public void setState(int state) { if (state == mState) return; if (state == STATE_REFRESHING) { // 顯示進度 mHeadArrowImage.clearAnimation(); mHeadArrowImage.setVisibility(View.INVISIBLE); mRefreshingProgress.setVisibility(View.VISIBLE); } else { // 顯示箭頭圖片 mHeadArrowImage.setVisibility(View.VISIBLE); mRefreshingProgress.setVisibility(View.INVISIBLE); } switch (state) { case STATE_NORMAL: if (mState == STATE_READY) { mHeadArrowImage.startAnimation(mRotateDownAnim); } if (mState == STATE_REFRESHING) { mHeadArrowImage.clearAnimation(); } mHeadHintTxt.setText("下拉刷新"); break; case STATE_READY: if (mState != STATE_READY) { mHeadArrowImage.clearAnimation(); mHeadArrowImage.startAnimation(mRotateUpAnim); mHeadHintTxt.setText("松開刷新"); } break; case STATE_REFRESHING: mHeadHintTxt.setText("正在刷新"); break; default: } mState = state; } public void setVisiableHeight(int height) { if (height < 0) height = 0; LayoutParams lp = (LayoutParams) mContainer .getLayoutParams(); lp.height = height; mContainer.setLayoutParams(lp); } public int getStatus() { return mState; } public int getVisiableHeight() { return mContainer.getHeight(); } }
最後是布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="bottom"> <RelativeLayout android:id="@+id/slistview_header_content" android:layout_width="match_parent" android:layout_height="60dp"> <LinearLayout android:id="@+id/slistview_header_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:gravity="center" android:orientation="vertical"> <TextView android:id="@+id/slistview_header_hint_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="下拉刷新" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="3dp"> <TextView android:id="@+id/slistview_header_last_refreash_txt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="上次刷新時間" android:textSize="12sp" /> <TextView android:id="@+id/slistview_header_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="12sp" /> </LinearLayout> </LinearLayout> <ProgressBar android:id="@+id/slistview_header_progressbar" android:layout_width="30dp" android:layout_height="30dp" android:layout_centerVertical="true" android:layout_toLeftOf="@id/slistview_header_text" android:visibility="invisible" /> <ImageView android:id="@+id/slistview_header_arrow" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@id/slistview_header_progressbar" android:layout_centerVertical="true" android:layout_toLeftOf="@id/slistview_header_text" android:src="@drawable/mmtlistview_arrow" /> </RelativeLayout> </LinearLayout>
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持本站。
折騰一下午,遇到若干問題:如,設置水平HORIZONTAL時,占用多個跨度(類似單元格的合並)終於都好用了。。。簡陋圖發6張。LinearLayoutManager.VE
說實話,關於Android中對短信的一些相關操作是一個比較入門的東西。那我現在還要來寫這一篇博客的原因只是因為現在開發中有相關內容,而又想將這些東西分享給更多的人來學習,
輪播圖是很常用的一個效果 核心功能已經實現 沒有什麼特殊需求 自己沒事研究的 所以封裝的不太好 一些地方還比較糙 為想要研究輪播圖的同學提供個參考目前測試圖片為mipma
bionic 目錄– libc (C庫)| |– arch-arm (ARM架構,包含系統調用匯編實現)| |– arch-x86 (