可下拉的PinnedHeaderExpandableListView的实现

前言

Android中,大家都用过ListView,ExpandableListView等,也许你还用过PinnedHeaderListView,但是如果我说PinnedHeaderExpandableListView,你听过吗?还有可下拉的PinnedHeaderExpandableListView呢?没听过也不要紧,本文就是介绍这个东西的,为了让大家有更直观的了解,先上效果图。通过效果图可以看出,首先它是一个ExpandableListView,但是它的头部可以固定,其次,在它的上面还有一个头部可以来回伸缩,恩,这就是本文要介绍的自定义view。为了提高复用性,这个效果我分成来了2个view来实现,第一个是PinnedHeaderExpandableListView来实现头部固定的ExpandableListView,第二个view是StickyLayout,这个view具有一个可以上下滑动的头部,最后将这2个view组合在一起,就达到了如下的效果。

PinnedHeaderExpandableListView的实现

关于ExpandableListView的使用方法请自己了解下,网上很多。关于这个view,它的实现方式是这样的:

首先继承自ExpandableListView,然后再它滚动的时候我们要监听顶部的item是属于哪个group的,当知道是哪个group以后,我们就在view的顶部绘制这个group,这样就完成了头部固定这个效果。当然过程远没有我描述的这个简单,期间有一些问题需要正确处理,下面分别说明:

1.如何知道顶部的item是哪个group,这个简单,略过;

 

2. 如何在顶部绘制group,这个我们可以重写dispatchDraw这个方法,在这个方法里drawChild即可,dispatchDraw是被draw方法用来绘制子元素的,和onDraw不同,onDraw是用来绘制自己的,我们要知道,view绘图的过程是先背景再自己最后在绘制子元素;

 

3. 滑动过程中header的更新,当滑动的时候,要去判断最上面的group是否发生改变,如果改变了就需要重新绘制group,这个很简单。注意到有一个效果,就是当两个group接近的时候,下面的group会把上面的header推上去,这个效果就难处理一些,推动的效果可以用layout来实现,通过layout将上面的group的位置给改变就可以了;

 

4.header的点击,要知道固定的头部是绘制上去的,并且它也不是ExpandableListView的子元素,可以理解为我们凭空绘制的一个view,如果处理它的点击,这个貌似很难,但是可以这么解决,当点击事件发生的时候,判断其区域是否落在header内部,如果落在了内部将可以处理点击事件了,处理后要讲事件消耗掉;

 

同时,我还提供了一个接口,OnHeaderUpdateListener,通过实现这个接口,PinnedHeaderExpandableListView就知道如何绘制和更新header了。下面看代码:

  1. /** 
  2. The MIT License (MIT) 
  3.  
  4. Copyright (c) 2014 singwhatiwanna 
  5. https://github.com/singwhatiwanna 
  6. http://blog.csdn.net/singwhatiwanna 
  7.  
  8. Permission is hereby granted, free of charge, to any person obtaining a copy 
  9. of this software and associated documentation files (the "Software"), to deal 
  10. in the Software without restriction, including without limitation the rights 
  11. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
  12. copies of the Software, and to permit persons to whom the Software is 
  13. furnished to do so, subject to the following conditions: 
  14.  
  15. The above copyright notice and this permission notice shall be included in all 
  16. copies or substantial portions of the Software. 
  17.  
  18. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
  19. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
  20. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
  21. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
  22. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
  23. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
  24. SOFTWARE. 
  25. */  
  26.   
  27. package com.ryg.expandable.ui;  
  28.   
  29. import android.content.Context;  
  30. import android.graphics.Canvas;  
  31. import android.util.AttributeSet;  
  32. import android.util.Log;  
  33. import android.view.MotionEvent;  
  34. import android.view.View;  
  35. import android.widget.AbsListView;  
  36. import android.widget.ExpandableListView;  
  37. import android.widget.AbsListView.OnScrollListener;  
  38.   
  39. public class PinnedHeaderExpandableListView extends ExpandableListView implements OnScrollListener {  
  40.     private static final String TAG = "PinnedHeaderExpandableListView";  
  41.   
  42.     public interface OnHeaderUpdateListener {  
  43.         /** 
  44.          * 采用单例模式返回同一个view对象即可 
  45.          * 注意:view必须要有LayoutParams 
  46.          */  
  47.         public View getPinnedHeader();  
  48.   
  49.         public void updatePinnedHeader(int firstVisibleGroupPos);  
  50.     }  
  51.   
  52.     private View mHeaderView;  
  53.     private int mHeaderWidth;  
  54.     private int mHeaderHeight;  
  55.   
  56.     private OnScrollListener mScrollListener;  
  57.     private OnHeaderUpdateListener mHeaderUpdateListener;  
  58.   
  59.     private boolean mActionDownHappened = false;  
  60.   
  61.   
  62.     public PinnedHeaderExpandableListView(Context context) {  
  63.         super(context);  
  64.         initView();  
  65.     }  
  66.   
  67.     public PinnedHeaderExpandableListView(Context context, AttributeSet attrs) {  
  68.         super(context, attrs);  
  69.         initView();  
  70.     }  
  71.   
  72.     public PinnedHeaderExpandableListView(Context context, AttributeSet attrs, int defStyle) {  
  73.         super(context, attrs, defStyle);  
  74.         initView();  
  75.     }  
  76.   
  77.     private void initView() {  
  78.         setFadingEdgeLength(0);  
  79.         setOnScrollListener(this);  
  80.     }  
  81.   
  82.     @Override  
  83.     public void setOnScrollListener(OnScrollListener l) {  
  84.         if (l != this) {  
  85.             mScrollListener = l;  
  86.         }  
  87.         super.setOnScrollListener(this);  
  88.     }  
  89.       
  90.     public void setOnHeaderUpdateListener(OnHeaderUpdateListener listener) {  
  91.         mHeaderUpdateListener = listener;  
  92.         if (listener == null) {  
  93.             return;  
  94.         }  
  95.         mHeaderView = listener.getPinnedHeader();  
  96.         int firstVisiblePos = getFirstVisiblePosition();  
  97.         int firstVisibleGroupPos = getPackedPositionGroup(getExpandableListPosition(firstVisiblePos));  
  98.         listener.updatePinnedHeader(firstVisibleGroupPos);  
  99.         requestLayout();  
  100.         postInvalidate();  
  101.     }  
  102.   
  103.     @Override  
  104.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  105.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  106.         if (mHeaderView == null) {  
  107.             return;  
  108.         }  
  109.         measureChild(mHeaderView, widthMeasureSpec, heightMeasureSpec);  
  110.         mHeaderWidth = mHeaderView.getMeasuredWidth();  
  111.         mHeaderHeight = mHeaderView.getMeasuredHeight();  
  112.     }  
  113.   
  114.     @Override  
  115.     protected void onLayout(boolean changed, int l, int t, int r, int b) {  
  116.         super.onLayout(changed, l, t, r, b);  
  117.         if (mHeaderView == null) {  
  118.             return;  
  119.         }  
  120.         mHeaderView.layout(00, mHeaderWidth, mHeaderHeight);  
  121.     }  
  122.   
  123.     @Override  
  124.     protected void dispatchDraw(Canvas canvas) {  
  125.         super.dispatchDraw(canvas);  
  126.         if (mHeaderView != null) {  
  127.             drawChild(canvas, mHeaderView, getDrawingTime());  
  128.         }  
  129.     }  
  130.   
  131.     @Override  
  132.     public boolean dispatchTouchEvent(MotionEvent ev) {  
  133.         int x = (int) ev.getX();  
  134.         int y = (int) ev.getY();  
  135.         Log.d(TAG, "dispatchTouchEvent");  
  136.         int pos = pointToPosition(x, y);  
  137.         if (y >= mHeaderView.getTop() && y <= mHeaderView.getBottom()) {  
  138.             if (ev.getAction() == MotionEvent.ACTION_DOWN) {  
  139.                 mActionDownHappened = true;  
  140.             } else if (ev.getAction() == MotionEvent.ACTION_UP) {  
  141.                 int groupPosition = getPackedPositionGroup(getExpandableListPosition(pos));  
  142.                 if (groupPosition != INVALID_POSITION && mActionDownHappened) {  
  143.                     if (isGroupExpanded(groupPosition)) {  
  144.                         collapseGroup(groupPosition);  
  145.                     } else {  
  146.                         expandGroup(groupPosition);  
  147.                     }  
  148.                     mActionDownHappened = false;  
  149.                 }  
  150.                   
  151.             }  
  152.             return true;  
  153.         }  
  154.   
  155.         return super.dispatchTouchEvent(ev);  
  156.     }  
  157.   
  158.     protected void refreshHeader() {  
  159.         if (mHeaderView == null) {  
  160.             return;  
  161.         }  
  162.         int firstVisiblePos = getFirstVisiblePosition();  
  163.         int pos = firstVisiblePos + 1;  
  164.         int firstVisibleGroupPos = getPackedPositionGroup(getExpandableListPosition(firstVisiblePos));  
  165.         int group = getPackedPositionGroup(getExpandableListPosition(pos));  
  166.   
  167.         if (group == firstVisibleGroupPos + 1) {  
  168.             View view = getChildAt(1);  
  169.             if (view.getTop() <= mHeaderHeight) {  
  170.                 int delta = mHeaderHeight - view.getTop();  
  171.                 mHeaderView.layout(0, -delta, mHeaderWidth, mHeaderHeight - delta);  
  172.             }  
  173.         } else {  
  174.             mHeaderView.layout(00, mHeaderWidth, mHeaderHeight);  
  175.         }  
  176.   
  177.         if (mHeaderUpdateListener != null) {  
  178.             mHeaderUpdateListener.updatePinnedHeader(firstVisibleGroupPos);  
  179.         }  
  180.     }  
  181.   
  182.     @Override  
  183.     public void onScrollStateChanged(AbsListView view, int scrollState) {  
  184.         if (mHeaderView != null && scrollState == SCROLL_STATE_IDLE) {  
  185.             int firstVisiblePos = getFirstVisiblePosition();  
  186.             if (firstVisiblePos == 0) {  
  187.                 mHeaderView.layout(00, mHeaderWidth, mHeaderHeight);  
  188.             }  
  189.         }  
  190.         if (mScrollListener != null) {  
  191.             mScrollListener.onScrollStateChanged(view, scrollState);  
  192.         }  
  193.     }  
  194.   
  195.     @Override  
  196.     public void onScroll(AbsListView view, int firstVisibleItem,  
  197.             int visibleItemCount, int totalItemCount) {  
  198.         if (totalItemCount > 0) {  
  199.             refreshHeader();  
  200.         }  
  201.         if (mScrollListener != null) {  
  202.             mScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);  
  203.         }  
  204.     }  
  205.   
  206. }  

下拉效果的实现

现在介绍第二个view,即StickyLayout,字面意思是黏性的layout,这个view内部分为2部分,header和content,并且header可以来回收缩。至于如何让header上下收缩,有几个看似可行的方案,我们分析下:

1.通过scrollTo/scrollBy来实现view的滚动,由于这两个api是对view内容的滚动,不管怎么滚动,内容都不会覆盖到别的view上去,除非你用了FrameLayout、RelativeLayout且经过精心布局,否则很难实现将内容滚动到别的view上面,即便如此,如果将header展开和收缩也是一个很大的问题,除非你动态地去调整header的布局,通过分析,这个方法不可行;

 

2. 通过动画来实现view的平移,从效果上来说,这个可行的,使用平移和缩放动画并结合手势的监听,可以实现这个效果,但是动画有一个问题,就是点击事件的处理,我们知道view动画,即使view区域发生了改变,但是事件点击区域仍然不变,而属性动画在3.0以下系统上根本不支持,就算采用兼容包,但是属性动画在3.0以下系统的点击事件区域仍然不会随着动画而改变,这更加证实了一个结论:动画是对view的显示发生作用,而不是view这个对象,也即是说动画并不影响view的区域(4个顶点)。说了这么多,好像还挺晦涩的,直白来说,采用动画来实现的问题是:在3.0以下系统,虽然view已经看起来跑到新位置了,但是你在新位置点击是不会触发点击事件的,而老位置还是可以触发点击事件,这就意味着,content移动后,content无法点击了,基于此,动画不可行;

 

3.第三种方案,也就是本文所采用的方案:通过手势监听结合header高度的改变来实现整个动画效果,具体点就是,当手指滑动的时候,动态去调整header的高度并重绘,这个时候由于header的高度发生了改变,所以content中的内容就会挤上去,就实现了本文中的效果了;

有了这个StickyLayout,想实现类似的效果,这要把可以收缩的内容放到header里,其他内容放到content里即可。下面看代码:

  1. /** 
  2. The MIT License (MIT) 
  3.  
  4. Copyright (c) 2014 singwhatiwanna 
  5. https://github.com/singwhatiwanna 
  6. http://blog.csdn.net/singwhatiwanna 
  7.  
  8. Permission is hereby granted, free of charge, to any person obtaining a copy 
  9. of this software and associated documentation files (the "Software"), to deal 
  10. in the Software without restriction, including without limitation the rights 
  11. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
  12. copies of the Software, and to permit persons to whom the Software is 
  13. furnished to do so, subject to the following conditions: 
  14.  
  15. The above copyright notice and this permission notice shall be included in all 
  16. copies or substantial portions of the Software. 
  17.  
  18. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
  19. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
  20. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
  21. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
  22. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
  23. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
  24. SOFTWARE. 
  25. */  
  26.   
  27. package com.ryg.expandable.ui;  
  28.   
  29. import java.util.NoSuchElementException;  
  30.   
  31. import android.annotation.TargetApi;  
  32. import android.content.Context;  
  33. import android.os.Build;  
  34. import android.util.AttributeSet;  
  35. import android.util.Log;  
  36. import android.view.MotionEvent;  
  37. import android.view.View;  
  38. import android.view.ViewConfiguration;  
  39. import android.widget.LinearLayout;  
  40.   
  41. public class StickyLayout extends LinearLayout {  
  42.     private static final String TAG = "StickyLayout";  
  43.   
  44.     public interface OnGiveUpTouchEventListener {  
  45.         public boolean giveUpTouchEvent(MotionEvent event);  
  46.     }  
  47.   
  48.     private View mHeader;  
  49.     private View mContent;  
  50.     private OnGiveUpTouchEventListener mGiveUpTouchEventListener;  
  51.   
  52.     // header的高度  单位:px  
  53.     private int mOriginalHeaderHeight;  
  54.     private int mHeaderHeight;  
  55.   
  56.     private int mStatus = STATUS_EXPANDED;  
  57.     public static final int STATUS_EXPANDED = 1;  
  58.     public static final int STATUS_COLLAPSED = 2;  
  59.   
  60.     private int mTouchSlop;  
  61.   
  62.     // 分别记录上次滑动的坐标  
  63.     private int mLastX = 0;  
  64.     private int mLastY = 0;  
  65.   
  66.     // 分别记录上次滑动的坐标(onInterceptTouchEvent)  
  67.     private int mLastXIntercept = 0;  
  68.     private int mLastYIntercept = 0;  
  69.   
  70.     // 用来控制滑动角度,仅当角度a满足如下条件才进行滑动:tan a = deltaX / deltaY > 2  
  71.     private static final int TAN = 2;  
  72.   
  73.     public StickyLayout(Context context) {  
  74.         super(context);  
  75.     }  
  76.   
  77.     public StickyLayout(Context context, AttributeSet attrs) {  
  78.         super(context, attrs);  
  79.     }  
  80.   
  81.     @TargetApi(Build.VERSION_CODES.HONEYCOMB)  
  82.     public StickyLayout(Context context, AttributeSet attrs, int defStyle) {  
  83.         super(context, attrs, defStyle);  
  84.     }  
  85.   
  86.     @Override  
  87.     public void onWindowFocusChanged(boolean hasWindowFocus) {  
  88.         super.onWindowFocusChanged(hasWindowFocus);  
  89.         if (hasWindowFocus && (mHeader == null || mContent == null)) {  
  90.             initData();  
  91.         }  
  92.     }  
  93.   
  94.     private void initData() {  
  95.         int headerId= getResources().getIdentifier("header""id", getContext().getPackageName());  
  96.         int contentId = getResources().getIdentifier("content""id", getContext().getPackageName());  
  97.         if (headerId != 0 && contentId != 0) {  
  98.             mHeader = findViewById(headerId);  
  99.             mContent = findViewById(contentId);  
  100.             mOriginalHeaderHeight = mHeader.getMeasuredHeight();  
  101.             mHeaderHeight = mOriginalHeaderHeight;  
  102.             mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();  
  103.             Log.d(TAG, "mTouchSlop = " + mTouchSlop);  
  104.         } else {  
  105.             throw new NoSuchElementException("Did your view with \"header\" or \"content\" exist?");  
  106.         }  
  107.     }  
  108.   
  109.     public void setOnGiveUpTouchEventListener(OnGiveUpTouchEventListener l) {  
  110.         mGiveUpTouchEventListener = l;  
  111.     }  
  112.   
  113.     @Override  
  114.     public boolean onInterceptTouchEvent(MotionEvent event) {  
  115.         int intercepted = 0;  
  116.         int x = (int) event.getX();  
  117.         int y = (int) event.getY();  
  118.   
  119.         switch (event.getAction()) {  
  120.         case MotionEvent.ACTION_DOWN: {  
  121.             mLastXIntercept = x;  
  122.             mLastYIntercept = y;  
  123.             mLastX = x;  
  124.             mLastY = y;  
  125.             intercepted = 0;  
  126.             break;  
  127.         }  
  128.         case MotionEvent.ACTION_MOVE: {  
  129.             int deltaX = x - mLastXIntercept;  
  130.             int deltaY = y - mLastYIntercept;  
  131.             if (mStatus == STATUS_EXPANDED && deltaY <= -mTouchSlop) {  
  132.                 intercepted = 1;  
  133.             } else if (mGiveUpTouchEventListener != null) {  
  134.                 if (mGiveUpTouchEventListener.giveUpTouchEvent(event) && deltaY >= mTouchSlop) {  
  135.                     intercepted = 1;  
  136.                 }  
  137.             }  
  138.             break;  
  139.         }  
  140.         case MotionEvent.ACTION_UP: {  
  141.             intercepted = 0;  
  142.             mLastXIntercept = mLastYIntercept = 0;  
  143.             break;  
  144.         }  
  145.         default:  
  146.             break;  
  147.         }  
  148.   
  149.         Log.d(TAG, "intercepted=" + intercepted);  
  150.         return intercepted != 0;  
  151.     }  
  152.   
  153.     @Override  
  154.     public boolean onTouchEvent(MotionEvent event) {  
  155.         int x = (int) event.getX();  
  156.         int y = (int) event.getY();  
  157.         Log.d(TAG, "x=" + x + "  y=" + y + "  mlastY=" + mLastY);  
  158.         switch (event.getAction()) {  
  159.         case MotionEvent.ACTION_DOWN: {  
  160.             break;  
  161.         }  
  162.         case MotionEvent.ACTION_MOVE: {  
  163.             int deltaX = x - mLastX;  
  164.             int deltaY = y - mLastY;  
  165.             Log.d(TAG, "mHeaderHeight=" + mHeaderHeight + "  deltaY=" + deltaY + "  mlastY=" + mLastY);  
  166.             mHeaderHeight += deltaY;  
  167.             setHeaderHeight(mHeaderHeight);  
  168.             break;  
  169.         }  
  170.         case MotionEvent.ACTION_UP: {  
  171.             // 这里做了下判断,当松开手的时候,会自动向两边滑动,具体向哪边滑,要看当前所处的位置  
  172.             int destHeight = 0;  
  173.             if (mHeaderHeight <= mOriginalHeaderHeight * 0.5) {  
  174.                 destHeight = 0;  
  175.                 mStatus = STATUS_COLLAPSED;  
  176.             } else {  
  177.                 destHeight = mOriginalHeaderHeight;  
  178.                 mStatus = STATUS_EXPANDED;  
  179.             }  
  180.             // 慢慢滑向终点  
  181.             this.smoothSetHeaderHeight(mHeaderHeight, destHeight, 500);  
  182.             break;  
  183.         }  
  184.         default:  
  185.             break;  
  186.         }  
  187.         mLastX = x;  
  188.         mLastY = y;  
  189.         return true;  
  190.     }  
  191.       
  192.     public void smoothSetHeaderHeight(final int from, final int to, long duration) {  
  193.         final int frameCount = (int) (duration / 1000f * 30) + 1;  
  194.         final float partation = (to - from) / (float) frameCount;  
  195.         new Thread("Thread#smoothSetHeaderHeight") {  
  196.   
  197.             @Override  
  198.             public void run() {  
  199.                 for (int i = 0; i < frameCount; i++) {  
  200.                     final int height;  
  201.                     if (i == frameCount - 1) {  
  202.                         height = to;  
  203.                     } else {  
  204.                         height = (int) (from + partation * i);  
  205.                     }  
  206.                     post(new Runnable() {  
  207.                         public void run() {  
  208.                             setHeaderHeight(height);  
  209.                         }  
  210.                     });  
  211.                     try {  
  212.                         sleep(10);  
  213.                     } catch (InterruptedException e) {  
  214.                         e.printStackTrace();  
  215.                     }  
  216.                 }  
  217.             };  
  218.   
  219.         }.start();  
  220.     }  
  221.   
  222.     private void setHeaderHeight(int height) {  
  223.         Log.d(TAG, "setHeaderHeight height=" + height);  
  224.         if (height < 0) {  
  225.             height = 0;  
  226.         } else if (height > mOriginalHeaderHeight) {  
  227.             height = mOriginalHeaderHeight;  
  228.         }  
  229.         if (mHeaderHeight != height || true) {  
  230.             mHeaderHeight = height;  
  231.             mHeader.getLayoutParams().height = mHeaderHeight;  
  232.             mHeader.requestLayout();  
  233.         }  
  234.     }  
  235.   
  236. }  

关于这个view还需要说明的是滑动冲突,如果content里是个listview,由于两者都能竖向滑动,这就会有冲突,如何解决滑动冲突一直是一个难点,我的解决思路是这样的:首先StickyLayout默认不拦截事件,如果子元素不处理事件,它就会上下滑动,如果子元素处理了事件,它就不会滑动,所以在最外层我们需要知道子元素何时处理事件、何时不处理事件,为了解决这个问题,提供了一个接口OnGiveUpTouchEventListener,当子元素不处理事件的时候,StickyLayout就可以处理滑动事件,具体请参看代码中的onInterceptTouchEvent和onTouchEvent。下面看一下activity对这2个接口的实现。

Activity的实现

由于Activity中大部分代码都是围绕ExpandableListAdapter,是比较普通的代码,这里要介绍的是activity对上述2个view中接口的实现,分别为PinnedHeaderExpandableListView中如何绘制和更新固定的头部以及StickyLayout中content何时放弃事件处理。

  1. @Override  
  2. public View getPinnedHeader() {  
  3.     if (mHeaderView == null) {  
  4.         mHeaderView = (ViewGroup) getLayoutInflater().inflate(R.layout.group, null);  
  5.         mHeaderView.setLayoutParams(new LayoutParams(  
  6.                 LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));  
  7.     }  
  8.     return mHeaderView;  
  9. }  
  10.   
  11. @Override  
  12. public void updatePinnedHeader(int firstVisibleGroupPos) {  
  13.     Group firstVisibleGroup = (Group) adapter.getGroup(firstVisibleGroupPos);  
  14.     TextView textView = (TextView) getPinnedHeader().findViewById(R.id.group);  
  15.     textView.setText(firstVisibleGroup.getTitle());  
  16. }  
  17.   
  18. @Override  
  19. public boolean giveUpTouchEvent(MotionEvent event) {  
  20.     if (expandableListView.getFirstVisiblePosition() == 0) {  
  21.         View view = expandableListView.getChildAt(0);  
  22.         if (view != null && view.getTop() >= 0) {  
  23.             return true;  
  24.         }  
  25.     }  
  26.     return false;  
  27. }  

总结

demo效果上还是不错的,在4.x和2.x上都经过测试,完美运行,市面上不少android应用有类似的效果,欢迎大家fork代码,欢迎大家交流。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在UniApp中,下拉刷新的实现可以借助于uni-scroll-view组件和下拉刷新事件来完成。 首先,在需要实现下拉刷新的页面的.vue文件中,引入uni-scroll-view组件,并在页面的布局中添加一个uni-scroll-view标签,设置其scroll-y属性为true,表示允许垂直方向的滚动。 ```html <uni-scroll-view class="scrollview" scroll-y="true"> <!-- 在这里放置页面的内容 --> </uni-scroll-view> ``` 接下来,在页面的script标签中,对下拉刷新事件进行监听,并在触发后执行相应的刷新操作。可以通过设置onPullDownRefresh属性来监听下拉刷新动作。 ```javascript export default { methods: { onPullDownRefresh() { // 执行刷新操作 // 可以在这里发送请求,获取最新的数据 // 刷新完成后,调用uni.stopPullDownRefresh()结束刷新动作 this.getData(); }, getData() { // 发送请求,获取最新的数据 // 请求结束后,更新页面数据 // 刷新完成后,调用uni.stopPullDownRefresh()结束刷新动作 uni.stopPullDownRefresh(); } } } ``` 在onPullDownRefresh回调函数中,可以调用相应的接口请求最新数据,并在请求结束后更新页面数据,最后使用uni.stopPullDownRefresh()方法结束下拉刷新动作。 需要注意的是,下拉刷新的效果是由uni-app底层自动实现的,我们只需要提供相应的监听函数和刷新逻辑即可。在实际使用过程中,我们还可以通过设置refresh-trigger属性,允许通过组件外部的手动方法来触发刷新动作,以满足各类需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值