• Android自定义控件(二)


    这一篇主要来讲一下自定义控件中的自定义viewgroup,我们以项目中最常用的下拉刷新和加载更多组件为例

    简单介绍一下自定义viewgroup时应该怎么做。

    分析:下拉刷新和加载更多的原理和步骤

    自定义一个viewgroup,将headerview、contentview和footerview从上到下依次布局,然后在初始化的时候

    通过Scrooller滚动使得该组件在y轴方向上滚动headerview的高度,这样headerview就被隐藏了。而contentview的

    宽度和高度都是match_parent的,因此屏幕上 headerview和footerview就都被隐藏在屏幕之外了。当contentview被

    滚动到顶部,如果此时用户继续下拉,那么下拉刷新组件将拦截触摸事件,然后根据用户的触摸事件获取到手指滑动的

    y轴距离,并通过scroller将该下拉组件在y轴上滚动手指滑动的距离,实现headerview的显示和隐藏,从而达到下拉的效果

    。当用户滑动到最底部时会触发加载更多的操作,此时会通过scroller滚动该下拉刷新组件,将footerview显示出来,实现加载更多

    的效果。具体步骤如下:

    第一步:初始化View即headerView contentView和footerView
    第二步:测量三个view的大小,并计算出viewgroup的大小
    第三步:布局,将三个view在界面上布局,按照上中下的顺序
    第四步:监听屏幕的触摸事件,判断是否下拉刷新或者加载更多
    第五步:触发下拉刷新和加载更多事件执行下拉刷新和加载更多
    第六步:下拉刷新和加载更多执行完后的重置操作

    示例代码:

    自定义的viewgroup

      1 package com.jiao.simpleimageview.view;
      2 
      3 import android.content.Context;
      4 import android.graphics.Color;
      5 import android.support.v4.view.MotionEventCompat;
      6 import android.util.AttributeSet;
      7 import android.view.LayoutInflater;
      8 import android.view.MotionEvent;
      9 import android.view.View;
     10 import android.view.ViewGroup;
     11 import android.view.animation.RotateAnimation;
     12 import android.widget.AbsListView;
     13 import android.widget.AbsListView.OnScrollListener;
     14 import android.widget.ImageView;
     15 import android.widget.ProgressBar;
     16 import android.widget.Scroller;
     17 import android.widget.TextView;
     18 
     19 import com.jiao.simpleimageview.R;
     20 import com.jiao.simpleimageview.listener.OnLoadListener;
     21 import com.jiao.simpleimageview.listener.OnRefreshListener;
     22 
     23 import java.text.SimpleDateFormat;
     24 import java.util.Date;
     25 
     26 /**
     27  * Created by jiaocg on 2016/3/24.
     28  */
     29 public abstract class RefreshLayoutBase<T extends View> extends ViewGroup implements
     30         OnScrollListener {
     31 
     32     /**
     33      *
     34      */
     35     protected Scroller mScroller;
     36 
     37     /**
     38      * 下拉刷新时显示的header view
     39      */
     40     protected View mHeaderView;
     41 
     42     /**
     43      * 上拉加载更多时显示的footer view
     44      */
     45     protected View mFooterView;
     46 
     47     /**
     48      * 本次触摸滑动y坐标上的偏移量
     49      */
     50     protected int mYOffset;
     51 
     52     /**
     53      * 内容视图, 即用户触摸导致下拉刷新、上拉加载的主视图. 比如ListView, GridView等.
     54      */
     55     protected T mContentView;
     56 
     57     /**
     58      * 最初的滚动位置.第一次布局时滚动header的高度的距离
     59      */
     60     protected int mInitScrollY = 0;
     61     /**
     62      * 最后一次触摸事件的y轴坐标
     63      */
     64     protected int mLastY = 0;
     65 
     66     /**
     67      * 空闲状态
     68      */
     69     public static final int STATUS_IDLE = 0;
     70 
     71     /**
     72      * 下拉或者上拉状态, 还没有到达可刷新的状态
     73      */
     74     public static final int STATUS_PULL_TO_REFRESH = 1;
     75 
     76     /**
     77      * 下拉或者上拉状态
     78      */
     79     public static final int STATUS_RELEASE_TO_REFRESH = 2;
     80     /**
     81      * 刷新中
     82      */
     83     public static final int STATUS_REFRESHING = 3;
     84 
     85     /**
     86      * LOADING中
     87      */
     88     public static final int STATUS_LOADING = 4;
     89 
     90     /**
     91      * 当前状态
     92      */
     93     protected int mCurrentStatus = STATUS_IDLE;
     94 
     95     /**
     96      * header中的箭头图标
     97      */
     98     private ImageView mArrowImageView;
     99     /**
    100      * 箭头是否向上
    101      */
    102     private boolean isArrowUp;
    103     /**
    104      * header 中的文本标签
    105      */
    106     private TextView mTipsTextView;
    107     /**
    108      * header中的时间标签
    109      */
    110     private TextView mTimeTextView;
    111     /**
    112      * header中的进度条
    113      */
    114     private ProgressBar mProgressBar;
    115     /**
    116      * 屏幕高度
    117      */
    118     private int mScreenHeight;
    119     /**
    120      * Header 高度
    121      */
    122     private int mHeaderHeight;
    123     /**
    124      * 下拉刷新监听器
    125      */
    126     protected OnRefreshListener mOnRefreshListener;
    127     /**
    128      * 加载更多回调
    129      */
    130     protected OnLoadListener mLoadListener;
    131 
    132     /**
    133      * @param context
    134      */
    135     public RefreshLayoutBase(Context context) {
    136         this(context, null);
    137     }
    138 
    139     /**
    140      * @param context
    141      * @param attrs
    142      */
    143     public RefreshLayoutBase(Context context, AttributeSet attrs) {
    144         this(context, attrs, 0);
    145     }
    146 
    147     /**
    148      * @param context
    149      * @param attrs
    150      * @param defStyle
    151      */
    152     public RefreshLayoutBase(Context context, AttributeSet attrs, int defStyle) {
    153         super(context, attrs);
    154 
    155         // 初始化Scroller对象
    156         mScroller = new Scroller(context);
    157 
    158         // 获取屏幕高度
    159         mScreenHeight = context.getResources().getDisplayMetrics().heightPixels;
    160         // header 的高度为屏幕高度的 1/4
    161         mHeaderHeight = mScreenHeight / 4;
    162 
    163         // 初始化整个布局
    164         initLayout(context);
    165     }
    166 
    167     /**
    168      * 第一步:初始化整个布局
    169      *
    170      * @param context
    171      */
    172     private final void initLayout(Context context) {
    173         // header view
    174         setupHeaderView(context);
    175         // 设置内容视图
    176         setupContentView(context);
    177         // 设置布局参数
    178         setDefaultContentLayoutParams();
    179         // 添加mContentView
    180         addView(mContentView);
    181         // footer view
    182         setupFooterView(context);
    183 
    184     }
    185 
    186     /**
    187      * 初始化 header view
    188      */
    189     protected void setupHeaderView(Context context) {
    190         mHeaderView = LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header, this,
    191                 false);
    192         mHeaderView
    193                 .setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT,
    194                         mHeaderHeight));
    195         mHeaderView.setBackgroundColor(Color.RED);
    196         mHeaderView.setPadding(0, mHeaderHeight - 100, 0, 0);
    197         addView(mHeaderView);
    198 
    199         // HEADER VIEWS
    200         mArrowImageView = (ImageView) mHeaderView.findViewById(R.id.pull_to_arrow_image);
    201         mTipsTextView = (TextView) mHeaderView.findViewById(R.id.pull_to_refresh_text);
    202         mTimeTextView = (TextView) mHeaderView.findViewById(R.id.pull_to_refresh_updated_at);
    203         mProgressBar = (ProgressBar) mHeaderView.findViewById(R.id.pull_to_refresh_progress);
    204     }
    205 
    206 
    207     /**
    208      * 初始化Content View, 子类覆写.
    209      */
    210     protected abstract void setupContentView(Context context);
    211 
    212     /**
    213      * 设置Content View的默认布局参数
    214      */
    215     protected void setDefaultContentLayoutParams() {
    216         ViewGroup.LayoutParams params =
    217                 new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT,
    218                         LayoutParams.MATCH_PARENT);
    219         mContentView.setLayoutParams(params);
    220     }
    221 
    222     /**
    223      * 初始化footer view
    224      */
    225     protected void setupFooterView(Context context) {
    226         mFooterView = LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_footer,
    227                 this, false);
    228         addView(mFooterView);
    229     }
    230 
    231 
    232     /**
    233      * 第二步:测量
    234      * 丈量视图的宽、高。宽度为用户设置的宽度,高度则为header,
    235      * content view, footer这三个子控件的高度之和。
    236      *
    237      * @param widthMeasureSpec
    238      * @param heightMeasureSpec
    239      */
    240     @Override
    241     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    242         int width = MeasureSpec.getSize(widthMeasureSpec);
    243         int childCount = getChildCount();
    244         int finalHeight = 0;
    245         for (int i = 0; i < childCount; i++) {
    246             View child = getChildAt(i);
    247             // measure
    248             measureChild(child, widthMeasureSpec, heightMeasureSpec);
    249             // 该view所需要的总高度
    250             finalHeight += child.getMeasuredHeight();
    251         }
    252         setMeasuredDimension(width, finalHeight);
    253     }
    254 
    255 
    256     /**
    257      * 第三步:布局
    258      * 布局函数,将header, content view,
    259      * footer这三个view从上到下布局。布局完成后通过Scroller滚动到header的底部,
    260      * 即滚动距离为header的高度 +本视图的paddingTop,从而达到隐藏header的效果.
    261      */
    262     @Override
    263     protected void onLayout(boolean changed, int l, int t, int r, int b) {
    264 
    265         int childCount = getChildCount();
    266         int top = getPaddingTop();
    267         for (int i = 0; i < childCount; i++) {
    268             View child = getChildAt(i);
    269             child.layout(0, top, child.getMeasuredWidth(), child.getMeasuredHeight() + top);
    270             top += child.getMeasuredHeight();
    271         }
    272 
    273         // 计算初始化滑动的y轴距离
    274         mInitScrollY = mHeaderView.getMeasuredHeight() + getPaddingTop();
    275         // 滑动到header view高度的位置, 从而达到隐藏header view的效果
    276         scrollTo(0, mInitScrollY);
    277     }
    278 
    279 
    280     /**
    281      * 第四步:监听滑动事件
    282      * 与Scroller合作,实现平滑滚动。在该方法中调用Scroller的computeScrollOffset来判断滚动是否结束。
    283      * 如果没有结束,
    284      * 那么滚动到相应的位置,并且调用postInvalidate方法重绘界面,
    285      * 从而再次进入到这个computeScroll流程,直到滚动结束。
    286      */
    287     @Override
    288     public void computeScroll() {
    289         if (mScroller.computeScrollOffset()) {
    290             scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
    291             postInvalidate();
    292         }
    293     }
    294 
    295     /*
    296      * 在适当的时候拦截触摸事件,这里指的适当的时候是当mContentView滑动到顶部,
    297      * 并且是下拉时拦截触摸事件,否则不拦截,交给其child
    298      * view 来处理。
    299      */
    300     @Override
    301     public boolean onInterceptTouchEvent(MotionEvent ev) {
    302 
    303         final int action = MotionEventCompat.getActionMasked(ev);
    304         // Always handle the case of the touch gesture being complete.
    305         if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
    306             // Do not intercept touch event, let the child handle it
    307             return false;
    308         }
    309 
    310         switch (action) {
    311 
    312             case MotionEvent.ACTION_DOWN:
    313                 mLastY = (int) ev.getRawY();
    314                 break;
    315 
    316             case MotionEvent.ACTION_MOVE:
    317                 // int yDistance = (int) ev.getRawY() - mYDown;
    318                 mYOffset = (int) ev.getRawY() - mLastY;
    319                 // 如果拉到了顶部, 并且是下拉,则拦截触摸事件,从而转到onTouchEvent来处理下拉刷新事件
    320                 if (isTop() && mYOffset > 0) {
    321                     return true;
    322                 }
    323                 break;
    324 
    325         }
    326         // Do not intercept touch event, let the child handle it
    327         return false;
    328     }
    329 
    330     /**
    331      * 第五步:下拉刷新
    332      * 1、滑动view显示出headerview
    333      * 2、进度条滚动,修改标题内容
    334      * 3、执行下拉刷新监听
    335      * 4、刷新成功或失败后重置:隐藏headerview 修改标题内容
    336      * 在这里处理触摸事件以达到下拉刷新或者上拉自动加载的问题
    337      *
    338      * @see android.view.View#onTouchEvent(android.view.MotionEvent)
    339      */
    340     @Override
    341     public boolean onTouchEvent(MotionEvent event) {//下拉刷新的处理
    342         switch (event.getAction()) {
    343             case MotionEvent.ACTION_MOVE:
    344                 int currentY = (int) event.getRawY();
    345                 mYOffset = currentY - mLastY;
    346                 if (mCurrentStatus != STATUS_LOADING) {
    347                     changeScrollY(mYOffset);
    348                 }
    349 
    350                 rotateHeaderArrow();//旋转箭头
    351                 changeTips();//重置文本
    352                 mLastY = currentY;
    353                 break;
    354 
    355             case MotionEvent.ACTION_UP:
    356                 // 下拉刷新的具体操作
    357                 doRefresh();
    358                 break;
    359             default:
    360                 break;
    361         }
    362         return true;
    363     }
    364 
    365     /**
    366      * 设置滚动的参数
    367      *
    368      * @param yOffset
    369      */
    370     private void startScroll(int yOffset) {
    371         mScroller.startScroll(getScrollX(), getScrollY(), 0, yOffset);
    372         invalidate();
    373     }
    374 
    375     /**
    376      * y轴上滑动到指定位置
    377      *
    378      * @param distance
    379      * @return
    380      */
    381     protected void changeScrollY(int distance) {
    382         // 最大值为 scrollY(header 隐藏), 最小值为0 ( header 完全显示).
    383         int curY = getScrollY();
    384         // 下拉
    385         if (distance > 0 && curY - distance > getPaddingTop()) {
    386             scrollBy(0, -distance);
    387         } else if (distance < 0 && curY - distance <= mInitScrollY) {
    388             // 上拉过程
    389             scrollBy(0, -distance);
    390         }
    391 
    392         curY = getScrollY();
    393         int slop = mInitScrollY / 2;
    394         //
    395         if (curY > 0 && curY < slop) {
    396             mCurrentStatus = STATUS_RELEASE_TO_REFRESH;
    397         } else if (curY > 0 && curY > slop) {
    398             mCurrentStatus = STATUS_PULL_TO_REFRESH;
    399         }
    400     }
    401 
    402 
    403     /**
    404      * 旋转箭头图标
    405      */
    406     protected void rotateHeaderArrow() {
    407 
    408         if (mCurrentStatus == STATUS_REFRESHING) {
    409             return;
    410         } else if (mCurrentStatus == STATUS_PULL_TO_REFRESH && !isArrowUp) {
    411             return;
    412         } else if (mCurrentStatus == STATUS_RELEASE_TO_REFRESH && isArrowUp) {
    413             return;
    414         }
    415 
    416         mProgressBar.setVisibility(View.GONE);
    417         mArrowImageView.setVisibility(View.VISIBLE);
    418         float pivotX = mArrowImageView.getWidth() / 2f;
    419         float pivotY = mArrowImageView.getHeight() / 2f;
    420         float fromDegrees = 0f;
    421         float toDegrees = 0f;
    422         if (mCurrentStatus == STATUS_PULL_TO_REFRESH) {
    423             fromDegrees = 180f;
    424             toDegrees = 360f;
    425         } else if (mCurrentStatus == STATUS_RELEASE_TO_REFRESH) {
    426             fromDegrees = 0f;
    427             toDegrees = 180f;
    428         }
    429 
    430         RotateAnimation animation = new RotateAnimation(fromDegrees, toDegrees, pivotX, pivotY);
    431         animation.setDuration(100);
    432         animation.setFillAfter(true);
    433         mArrowImageView.startAnimation(animation);
    434 
    435         if (mCurrentStatus == STATUS_RELEASE_TO_REFRESH) {
    436             isArrowUp = true;
    437         } else {
    438             isArrowUp = false;
    439         }
    440     }
    441 
    442     /**
    443      * 根据当前状态修改header view中的文本标签
    444      */
    445     protected void changeTips() {
    446         if (mCurrentStatus == STATUS_PULL_TO_REFRESH) {
    447             mTipsTextView.setText(R.string.pull_to_refresh_pull_label);
    448         } else if (mCurrentStatus == STATUS_RELEASE_TO_REFRESH) {
    449             mTipsTextView.setText(R.string.pull_to_refresh_release_label);
    450         }
    451     }
    452 
    453 
    454     /**
    455      * 手指抬起时,根据用户下拉的高度来判断是否是有效的下拉刷新操作。
    456      * 如果下拉的距离超过header view的
    457      * 1/2那么则认为是有效的下拉刷新操作,否则恢复原来的视图状态.
    458      */
    459     private void changeHeaderViewStaus() {
    460         int curScrollY = getScrollY();
    461         // 超过1/2则认为是有效的下拉刷新, 否则还原
    462         if (curScrollY < mInitScrollY / 2) {
    463             mScroller.startScroll(getScrollX(), curScrollY, 0, mHeaderView.getPaddingTop()
    464                     - curScrollY);
    465             mCurrentStatus = STATUS_REFRESHING;
    466             mTipsTextView.setText(R.string.pull_to_refresh_refreshing_label);
    467             mArrowImageView.clearAnimation();
    468             mArrowImageView.setVisibility(View.GONE);
    469             mProgressBar.setVisibility(View.VISIBLE);
    470         } else {
    471             mScroller.startScroll(getScrollX(), curScrollY, 0, mInitScrollY - curScrollY);
    472             mCurrentStatus = STATUS_IDLE;
    473         }
    474 
    475         invalidate();
    476     }
    477 
    478     /**
    479      * 执行下拉刷新
    480      */
    481     protected void doRefresh() {
    482         changeHeaderViewStaus();
    483         // 执行刷新操作
    484         if (mCurrentStatus == STATUS_REFRESHING && mOnRefreshListener != null) {
    485             mOnRefreshListener.onRefresh();
    486         }
    487     }
    488 
    489     /**
    490      * 刷新结束,恢复状态
    491      */
    492     public void refreshComplete() {
    493         mScroller.startScroll(getScrollX(), getScrollY(), 0, mInitScrollY - getScrollY());
    494         mCurrentStatus = STATUS_IDLE;
    495         invalidate();
    496         updateHeaderTimeStamp();
    497 
    498         // 200毫秒后处理arrow和progressbar,免得太突兀
    499         this.postDelayed(new Runnable() {
    500 
    501             @Override
    502             public void run() {
    503                 mArrowImageView.setVisibility(View.VISIBLE);
    504                 mProgressBar.setVisibility(View.GONE);
    505             }
    506         }, 100);
    507 
    508     }
    509 
    510     /**
    511      * 修改header上的最近更新时间
    512      */
    513     private void updateHeaderTimeStamp() {
    514         // 设置更新时间
    515         mTimeTextView.setText(R.string.pull_to_refresh_update_time_label);
    516         SimpleDateFormat sdf = (SimpleDateFormat) SimpleDateFormat.getInstance();
    517         sdf.applyPattern("yyyy-MM-dd HH:mm:ss");
    518         mTimeTextView.append(sdf.format(new Date()));
    519     }
    520 
    521 
    522     /**
    523      * 第六步:加载更多
    524      * 滚动监听,当滚动到最底部,且用户设置了加载更多的监听器时触发加载更多操作.
    525      * AbsListView, int, int, int)
    526      */
    527     @Override
    528     public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
    529                          int totalItemCount) {
    530         // 用户设置了加载更多监听器,且到了最底部,并且是上拉操作,那么执行加载更多.
    531         if (mLoadListener != null && isBottom() && mScroller.getCurrY() <= mInitScrollY
    532                 && mYOffset <= 0
    533                 && mCurrentStatus == STATUS_IDLE) {
    534             showFooterView();
    535             doLoadMore();
    536         }
    537     }
    538 
    539 
    540     @Override
    541     public void onScrollStateChanged(AbsListView view, int scrollState) {
    542 
    543     }
    544 
    545     /**
    546      * 执行下拉(自动)加载更多的操作
    547      */
    548     protected void doLoadMore() {
    549         if (mLoadListener != null) {
    550             mLoadListener.onLoadMore();
    551         }
    552     }
    553     /**
    554      * 显示footer view
    555      */
    556     private void showFooterView() {
    557         startScroll(mFooterView.getMeasuredHeight());
    558         mCurrentStatus = STATUS_LOADING;
    559     }
    560 
    561     /**
    562      * 加载结束,恢复状态
    563      */
    564     public void loadCompelte() {
    565         // 隐藏footer
    566         startScroll(mInitScrollY - getScrollY());
    567         mCurrentStatus = STATUS_IDLE;
    568     }
    569 
    570 
    571     /**
    572      * 设置下拉刷新监听器
    573      *
    574      * @param listener
    575      */
    576     public void setOnRefreshListener(OnRefreshListener listener) {
    577         mOnRefreshListener = listener;
    578     }
    579 
    580     /**
    581      * 设置滑动到底部时自动加载更多的监听器
    582      *
    583      * @param listener
    584      */
    585     public void setOnLoadListener(OnLoadListener listener) {
    586         mLoadListener = listener;
    587     }
    588 
    589 
    590     /**
    591      * 是否已经到了最顶部,子类需覆写该方法,使得mContentView滑动到最顶端时返回true, 如果到达最顶端用户继续下拉则拦截事件;
    592      *
    593      * @return
    594      */
    595     protected abstract boolean isTop();
    596 
    597     /**
    598      * 是否已经到了最底部,子类需覆写该方法,使得mContentView滑动到最底端时返回true;从而触发自动加载更多的操作
    599      *
    600      * @return
    601      */
    602     protected abstract boolean isBottom();
    603 
    604 
    605     /**
    606      * 返回Content View
    607      *
    608      * @return
    609      */
    610     public T getContentView() {
    611         return mContentView;
    612     }
    613 
    614     /**
    615      * @return
    616      */
    617     public View getHeaderView() {
    618         return mHeaderView;
    619     }
    620 
    621     /**
    622      * @return
    623      */
    624     public View getFooterView() {
    625         return mFooterView;
    626     }
    627 
    628 }

    实现下拉刷新的listview

     1 package com.jiao.simpleimageview.view;
     2 
     3 import android.content.Context;
     4 import android.util.AttributeSet;
     5 import android.widget.ListAdapter;
     6 import android.widget.ListView;
     7 
     8 /**
     9  * Created by jiaocg on 2016/3/25.
    10  */
    11 public class RefreshListView extends RefreshLayoutBase<ListView> {
    12     /**
    13      * @param context
    14      */
    15     public RefreshListView(Context context) {
    16         this(context, null);
    17     }
    18 
    19     /**
    20      * @param context
    21      * @param attrs
    22      */
    23     public RefreshListView(Context context, AttributeSet attrs) {
    24         this(context, attrs, 0);
    25     }
    26 
    27     /**
    28      * @param context
    29      * @param attrs
    30      * @param defStyle
    31      */
    32     public RefreshListView(Context context, AttributeSet attrs, int defStyle) {
    33         super(context, attrs, defStyle);
    34     }
    35 
    36     @Override
    37     protected void setupContentView(Context context) {
    38         mContentView = new ListView(context);
    39         // 设置滚动监听器
    40         mContentView.setOnScrollListener(this);
    41 
    42     }
    43 
    44     @Override
    45     protected boolean isTop() {
    46 
    47         //当第一个可见项是第一项时表示已经拉倒了顶部
    48         return mContentView.getFirstVisiblePosition() == 0
    49                 && getScrollY() <= mHeaderView.getMeasuredHeight();
    50     }
    51 
    52     @Override
    53     protected boolean isBottom() {
    54         //当最后一个可见项是最后一项时表示已经拉倒了底部
    55         return mContentView != null && mContentView.getAdapter() != null
    56                 && mContentView.getLastVisiblePosition() ==
    57                 mContentView.getAdapter().getCount() - 1;
    58     }
    59 
    60     /**
    61      * 设置adapter
    62      */
    63     public void setAdapter(ListAdapter adapter) {
    64         mContentView.setAdapter(adapter);
    65     }
    66 
    67     public ListAdapter getAdapter() {
    68         return mContentView.getAdapter();
    69     }
    70 
    71 }

    然后直接在xml文件中引用使用即可实现,另外这种方式的下拉刷新扩展性很强

    也可以实现TextView和GridView的刷新,只需继承该base实现其中的抽象方法即可

    源码下载:https://yunpan.cn/cqKRSr2r2MsEk  提取密码:d177

  • 相关阅读:
    Springboot 报错 Invalid character found in the request target. The valid characters are defined in RFC 7230 and RFC 3986
    layui时间戳转日期踩坑
    前端 传入Date 为空的解决办法
    解决springdatajpa设置默认值保存null无效的问题
    2020-04-12工作记录
    js原型链继承的傻瓜式详解
    一个关于python装饰器参数的问题
    【转】Unicode utf8等编码类型的原理
    c/c++内存泄露的检测方法
    【转】什么是动态规划?动态规划的意义是什么
  • 原文地址:https://www.cnblogs.com/all88/p/5329749.html
Copyright © 2020-2023  润新知