English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
项目中需要下拉刷新的功能,但是这个View不是ListView这类控件,需要ViewGroup实现这个功能,一开始网上大略找了一下,没发现特别合适的,代码也是没怎么看懂,所以决定还是自己写一个。
于是翻出XlistView的源码看是一点一点看,再大致理解了XLisview源码,终于决定自己动手啦
为了省事,headView还是用了XListView的HeadView,省了很多事:)
下拉刷新,下拉刷新,肯定是先实现下拉功能,最开始我是打算通过 extends ScrollView 来实现,因为有现成的滚动效果嘛,可是实际因为两个原因放弃了:
1The ScrollView can only have one child View, although you can add a ViewGroup under Scroll and then dynamically add headView into the preceding ViewGroup, but I still prefer the visual preview of studio, it always feels not intuitive!
2When a ListView is nested inside a ScrollView, conflicts will occur, and you also need to override the ListView. So let's give up and change our perspective!
Regarding the above reason1Dynamic addition of headView into the ScrollView's GroupView, you can override the ScrollView's onViewAdded() method to add the initialized headView into the child GroupView
@Override public void onViewAdded(View child) { super.onViewAdded(child); //Since headView needs to be on top, the first thing that comes to mind is a Vertical LinearLayout LinearLayout linearLayout = (LinearLayout) getChildAt(0); linearLayout.addView(view, 0); }
Let's change our perspective and implement it by extending LinearLayout!
First do the preparation work, we need a HeaderView and the height of the HeaderView to be obtained, as well as the initial height of the 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 is instantiated through the constructor
mHeaderViewContent = (RelativeLayout)
mHeaderView.findViewById(R.id.slistview_header_content);
This is the parsing of the headerView content area view, we will need to obtain the height of this view later, you may ask why not use the mHeaderView above to obtain the height, you can see the following code by entering the constructor
// Initial condition, set the height of the pull-down refresh view to 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);
If you directly obtain the height of mHeaderView, it must be 0
getHeaderViewHeight();
getViewHeight();
They are to obtain the height of the HeaderView and the initial height of the Layout
/** * Get the height of headView */ private void getHeaderViewHeight() { ViewTreeObserver vto2 = mHeaderViewContent.getViewTreeObserver(); vto2.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { mHeaderViewHeight = mHeaderViewContent.getHeight(); mHeaderViewContent.getViewTreeObserver().removeGlobalOnLayoutListener(this); } }); } /** * Get the current height of SRefreshLayout instance */ 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); } }); }
Preparation is complete, and the next step is to perform the pull-down operation
By now, you must have thought of the onTouchEvent() method, yes! Let's start working here right away
Implementing the pull-down will go through three processes in total
ACTION_UP→ACTION_MOVE→ACTION_UP
在ACTION_UP事件中,也就是手指按下的时候,我们需要做的只是记录按下时的坐标
switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: //Die Anfangshöhe aufzeichnen mLastY = ev.getRawY();//Die Y-Koordinate beim Drücken aufzeichnen 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);//Verkleinere den Bewegungsabstand in einem bestimmten Verhältnis updateHeight(); break;
里面的updateHeaderViewHeight和updateHeight分别是改变HeaderView的高度和Layout的高度
private void updateHeight() { ViewGroup.LayoutParams lp = getLayoutParams(); //aktualisiere die Höhe des aktuellen Layout-Beispiels auf die Höhe des HeaderViews plus der ursprünglichen Layout-Höhe //wenn das Layout nicht aktualisiert wird, wird die Inhaltshöhe komprimiert, was das Verhältnis beeinträchtigen kann 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) { //wenn nicht im Ladezustand ist und wenn die Höhe 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: //Beim Loslassen //Vermeiden Sie, dass das Klickereignis ausgelöst wird if (!isRefreashing) break; //Wenn der Zustand von headView im READY-Status ist, bedeutet dies, dass beim Loslassen der REFRESHING-Status eintreten sollte if (mHeaderView.getStatus() == SRefreshHeader.STATE_READY) { mHeaderView.setState(SRefreshHeader.STATE_REFRESHING); } //Die aktuelle Instanz von SrefreshLayout und die Höhe von headView basierend auf dem Status zurücksetzen resetHeadView(mHeaderView.getStatus()); reset(mHeaderView.getStatus()); mLastY = -1;//Zielkoordinaten zurücksetzen 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: //Beim Loslassen //Vermeiden Sie, dass das Klickereignis ausgelöst wird if (!isRefreashing) break; //Wenn der Zustand von headView im READY-Status ist, bedeutet dies, dass beim Loslassen der REFRESHING-Status eintreten sollte if (mHeaderView.getStatus() == SRefreshHeader.STATE_READY) { mHeaderView.setState(SRefreshHeader.STATE_REFRESHING); if (mOnRefreshListener != null) mOnRefreshListener.onRefresh(); } //Die aktuelle Instanz von SrefreshLayout und die Höhe von headView basierend auf dem Status zurücksetzen resetHeadView(mHeaderView.getStatus()); reset(mHeaderView.getStatus()); mLastY = -1;//Zielkoordinaten zurücksetzen 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("Found 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("Blockieren erfolgreich"); return true; } else { Logs.v("Nicht blockieren"); return false; } } return super.onInterceptTouchEvent(ev); }
其中
if (space > 0 && !absListView.canScrollVertically(-1) && absListView.getFirstVisiblePosition() == 0)
space表示移动的距离,canScrollVertically()用于判断ListView是否能在垂直方向上滚动,参数为负数时表示向上滚动,为正数时表示向下滚动,最后一个参数表示ListView第一个可见的item的位置
With the above event interception processing, a Viewgroup that meets the requirements mentioned at the beginning is also completed!
Below is the source code of the Layout and the HeaderView (the HeaderView directly used by XlistView) source code
public class SRefreshLayout extends LinearLayout { private SRefreshHeader mHeaderView; private RelativeLayout mHeaderViewContent; private boolean isRefreashing; private float mLastY = -1;//Initial Press Height private int mHeaderViewHeight;//HeaderView Content Height private int mHeight;//Layout Height 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(); } /** * Get the height of headView */ private void getHeaderViewHeight() { ViewTreeObserver vto2 = mHeaderViewContent.getViewTreeObserver(); vto2.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { mHeaderViewHeight = mHeaderViewContent.getHeight(); mHeaderViewContent.getViewTreeObserver().removeGlobalOnLayoutListener(this); } }); } /** * Get the current height of SRefreshLayout instance */ 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("Found 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("Blockieren erfolgreich"); return true; } else { Logs.v("Nicht blockieren"); 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: //Die Anfangshöhe aufzeichnen mLastY = ev.getRawY();//Die Y-Koordinate beim Drücken aufzeichnen break; //Wenn der Finger die Bildschirmfläche verlässt case MotionEvent.ACTION_UP: //Beim Loslassen //Vermeiden Sie, dass das Klickereignis ausgelöst wird if (!isRefreashing) break; //Wenn der Zustand von headView im READY-Status ist, bedeutet dies, dass beim Loslassen der REFRESHING-Status eintreten sollte if (mHeaderView.getStatus() == SRefreshHeader.STATE_READY) { mHeaderView.setState(SRefreshHeader.STATE_REFRESHING); if (mOnRefreshListener != null) mOnRefreshListener.onRefresh(); } //Die aktuelle Instanz von SrefreshLayout und die Höhe von headView basierend auf dem Status zurücksetzen resetHeadView(mHeaderView.getStatus()); reset(mHeaderView.getStatus()); mLastY = -1;//Zielkoordinaten zurücksetzen break; case MotionEvent.ACTION_MOVE: if (!isRefreashing) isRefreashing = true; final float deltaY = ev.getRawY(); - mLastY; mLastY = ev.getRawY(); updateHeaderViewHeight(deltaY / 1.8f);//Verkleinere den Bewegungsabstand in einem bestimmten Verhältnis 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(); //aktualisiere die Höhe des aktuellen Layout-Beispiels auf die Höhe des HeaderViews plus der ursprünglichen Layout-Höhe //wenn das Layout nicht aktualisiert wird, wird die Inhaltshöhe komprimiert, was das Verhältnis beeinträchtigen kann 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) { //wenn nicht im Ladezustand ist und wenn die Höhe 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) { // Initial condition, set the height of the pull-down refresh view to 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) { // Anzeige des Fortschritts mHeadArrowImage.clearAnimation(); mHeadArrowImage.setVisibility(View.INVISIBLE); mRefreshingProgress.setVisibility(View.VISIBLE); } else { // Anzeigebild der Pfeil 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("Nach unten ziehen, um zu aktualisieren"); break; case STATE_READY: if (mState != STATE_READY) {}} mHeadArrowImage.clearAnimation(); mHeadArrowImage.startAnimation(mRotateUpAnim); mHeadHintTxt.setText("Loslassen, um zu aktualisieren"); } break; case STATE_REFRESHING: mHeadHintTxt.setText("Wird aktualisiert"); 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(); } }
Der letzte ist die Layout-Datei
<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="Nach unten ziehen und aktualisieren" /> <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="Letztes Aktualisierungszeitpunkt" 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>
Das ist der gesamte Inhalt dieses Artikels. Wir hoffen, dass er Ihnen bei Ihrem Lernen hilft und dass Sie die Anleitung weiterempfehlen.
Erklärung: Der Inhalt dieses Artikels wurde aus dem Internet bezogen und gehört dem Urheberrecht des jeweiligen Autors. Der Inhalt wurde von Internetbenutzern freiwillig beigesteuert und hochgeladen. Diese Website besitzt keine Eigentumsrechte und hat den Inhalt nicht manuell bearbeitet. Sie übernimmt auch keine rechtlichen Verantwortlichkeiten. Wenn Sie verdächtige Urheberrechtsinhalte finden, sind Sie herzlich eingeladen, eine E-Mail an notice#w zu senden.3codebox.com (Bitte ersetzen Sie # durch @, wenn Sie eine E-Mail senden, um zu melden, und fügen Sie relevante Beweise bei. Sobald nachgewiesen, wird diese Website den涉嫌侵权的内 容立即删除。)