美文网首页
Android之轮播图与下拉、上拉刷新

Android之轮播图与下拉、上拉刷新

作者: 破荒之恋 | 来源:发表于2016-12-24 13:48 被阅读245次

    轮播图与下拉、上拉刷新

    Handler

    1. handler : 发送消息和处理消息
    2. Message : 消息
    3. MessageQueue : 存储消息的队列
    4. Looper : 轮询器

    轮播图的实现

    在一个布局中嵌入一个ViewPager,ViewPager里面出现轮播图的效果,这个如何实现的呢?

    1. 首先定义一个类继承ViewPager,实现他的所有构造函数。

    2. 重写ViewPager的dispatchTouchEvent这个方法,在这里可以判断实现轮播图自动轮播,触摸停止,手动滑动的效果。在ViewPager自己响应Touch事件时就可以实现手动滑动。自己不响应Touch事件时交由父类去响应Touch事件。有这么几种情况:

       //1、从右往左
       //如果是第一个页面,手指从右往左移动,自已响应自己响应touch
       //如果是第二个页面,手指从右往左滑动,进入下一个页面,自己响应touch
       
       //如果是最后一个页面,手指从右往左滑动,进入下一个页面父容器touch
       
       //2、从左往右
       //如果是在第一个页面,手指从左往右, 父容器响应
       
       //如果是第二个页面,手指从左往右,自己响应touch
       
       //如果是最后一个页面,手指从右往左, 自己响应touch
      

    现在来看看代码怎么实现这几种情况的:

    public class HorizontalScrollViewPager extends ViewPager
    {
    
        private float   downX;
        private float   downY;
        public HorizontalScrollViewPager(Context context, AttributeSet attrs) {
            
            super(context, attrs);
        }
    
        public HorizontalScrollViewPager(Context context) {
            
            super(context);
        }
        @Override
        public boolean dispatchTouchEvent(MotionEvent ev)
        {
    
            switch(ev.getAction()){
                case MotionEvent.ACTION_DOWN:  //手指按下ViewPageer时
                    //获得按下时XY轴的坐标
                    downX = ev.getX();
                    downY = ev.getY();
                    
                    break;
                case MotionEvent.ACTION_UP: //抬起触摸ViewPager的手指时
                                
                    break;
                case MotionEvent.ACTION_MOVE: //在ViewPager里面滑动手指时
    
                //请求父类不要拦截Touch事件,自己响应
                    getParent().requestDisallowInterceptTouchEvent(true);
                    float moveX=ev.getX();
                    float moveY=ev.getY();
                    
                    float diffx=moveX-downX;
                    float diffy=moveY-downY;
                    
                    //Touch的响应逻辑
                    if(Math.abs(diffx)>Math.abs(diffy)){
    
                    //用户手指从左往右
                    //如果是在第一个页面,手指从左往右, 父容器响应
    
                        if(diffx>0 && getCurrentItem()==0){
                            getParent().requestDisallowInterceptTouchEvent(false);
                        }else if(diffx>0 && getCurrentItem()!=0){
    
                    //如果是在除去第一个页面外,手指从左往右滑动,自己响应touch
                            getParent().requestDisallowInterceptTouchEvent(true);
                        }else if(diffx<0 && (getAdapter().getCount()-1)==getCurrentItem()){
    
                    //最后一个页面,手指从右往左滑动,父容器响应
                            getParent().requestDisallowInterceptTouchEvent(false);
                        }else{
    
                    //从右往左
                    //如果在第一个页面,手指进入第二个页面,自己响应touch
                            getParent().requestDisallowInterceptTouchEvent(true);
                        }
                    }else{
    
                        //touch交给父容器
                        getParent().requestDisallowInterceptTouchEvent(false);
                    }
                    break;
            }
            return super.dispatchTouchEvent(ev);
        }
    }
    

    在代码中调用这个requestDisallowInterceptTouchEvent()方法中,传入true就是要求父容器不要响应Touch事件,传入false就是允许父容器拦截Touch事件。

    在使用这个类时要充分考虑到它的延时。

    private AutoSwitchPicTask   mswitchPicTask;
    
    //4、处理延时轮播
        if(mswitchPicTask==null){
        mswitchPicTask = new AutoSwitchPicTask();
        }
        mswitchPicTask.start();
        
        //5、mPager设置touch监听,触摸时停止轮播,抬起时开始轮播
        mPager.setOnTouchListener(new OnTouchListener() {
            
            @Override
            public boolean onTouch(View v, MotionEvent event)
            {
                
                switch(event.getAction()){
                    case MotionEvent.ACTION_DOWN:
                        mswitchPicTask.stop();//停止轮播
                        break;
                    case MotionEvent.ACTION_UP:
                    case MotionEvent.ACTION_CANCEL:
                        mswitchPicTask.start();//开始轮播
                        break;
                    default :
                            break;
                        
                }
                return false;
            }
        });
    
    //设置轮播时的处理
    class AutoSwitchPicTask extends Handler implements Runnable{
        public void run()
        {
            //让viewpager选中下一个
            int item=mPager.getCurrentItem();
            if(item==mPager.getAdapter().getCount()-1){
                item=-1;
            }
            mPager.setCurrentItem(++item);
            postDelayed(this, TIME_DELAY);
            
        }
        public void start()
        {
            stop();
            postDelayed(this, TIME_DELAY);
        }
        public void stop()
        {
            removeCallbacksAndMessages(null);
    
        }
    }
    

    ViewPager设置适配器:

        //设置适配器
    class NewsListPagerAdapter extends PagerAdapter
    {
        @Override
        public int getCount()
        {
            if (mPicData != null) { return mPicData.size(); }
            return 0;
        }
        @Override
        public boolean isViewFromObject(View arg0, Object arg1)
        {
            return arg0 == arg1;
        }
        // 实例化一个页卡
        @Override
        public Object instantiateItem(ViewGroup container, int position)
        {
            ImageView iv = new ImageView(mContext);
            iv.setScaleType(ScaleType.FIT_XY); // 缩放的类型,填充
            iv.setImageResource(R.drawable.pic_item_list_default);
            //設置网络图片
            NewsListPagerTopnesBean bean = mPicData.get(position);
             String imguri = bean.topimage;
            
            // 网络加载图片数据
            mBitmapUtils.display(iv, imguri);
            container.addView(iv);
            return iv;
    }
        @Override
        public void destroyItem(ViewGroup container, int position, Object object)
        {
            container.removeView((View) object);
        }
    }
    

    要想在viewpager上面切换图片显示不同的文字文字,可以设置一个监听器setOnPageChangeListener(),当页面改变时触发这个方法。

    下拉刷新

    1. 实现原理: 通过设置listView 的headerLayout 的paddingTop来实现

    2. 控件的测量:

      1. measure: 测量控件的宽度和高度(widthMeasureSpec,heightMeasureSpec)
        1. 32位的01010101010101010
      2. MeasureSpec :
        1. mode:
          1. EXACTLY 30dp
          2. AT_MOST 100dp
          3. UNSPECIFIED
        2. size: 实际大小
    3. 刷新状态的介绍:

      1. 需要下拉刷新
      2. 释放刷新
      3. 正在刷新

    现在来一步一步解析:

    下拉刷新的原理就是在一个布局中定义了刷新的View和显示的View,相当于头布局和脚步局

    而一般使用下拉刷新的是listView家在很多数据时才使用得到。

    1. 先定义一个类继承listView如 RefreshListView extends ListView 并实现所有的构造函数,在构造函数里加载头布局,定义一个方法:

        //加载头布局
        initHeaderLayout();
    

    头布局:刷新的view布局refresh_listview_header.xml

        <?xml version="1.0" encoding="utf-8"?>
        <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical" >
        
            <!-- listview的listviewHeader部分 -->
        
            <RelativeLayout
                    android:id="@+id/refresh_header_part"
                android:layout_width="match_parent"
                android:layout_height="100dp" >
        
                <FrameLayout
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_centerVertical="true"
                    android:layout_marginLeft="10dp" >
        
                    <ProgressBar
                         android:id="@+id/refresh_header_pb"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:visibility="gone"
                        android:layout_gravity="center" />
        
                    <ImageView
                         android:id="@+id/refresh_header_arrow"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_gravity="center"
                        android:src="@drawable/common_listview_headview_red_arrow" />
                </FrameLayout>
        
                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_centerInParent="true"
                    android:orientation="vertical" >
        
                    <TextView
                         android:id="@+id/refresh_header_state"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_gravity="center"
                        android:text="下拉刷新"
                        android:textColor="#ff0000"
                        android:textSize="20sp" />
        
                    <TextView
                        android:id="@+id/refresh_header_date"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_gravity="center"
                        android:text="下拉时间"
                        android:textColor="#000000"
                        android:textSize="15sp" />
                </LinearLayout>
            </RelativeLayout>
        
        </LinearLayout>
    

    2. 在实现加载头布局的逻辑:

        private void initHeaderLayout()
        {
            // 加载头布局
            mHeaderlayout = (LinearLayout) View.inflate(getContext(), R.layout.refresh_listview_header, null);
            // 添加到Headerview到listview中
            this.addHeaderView(mHeaderlayout);
    
            // 需要隐藏刷新的布局view
            mRefreshView = mHeaderlayout.findViewById(R.id.refresh_header_part);
            mProssBar = (ProgressBar) mHeaderlayout.findViewById(R.id.refresh_header_pb);
            mArrow = (ImageView) mHeaderlayout.findViewById(R.id.refresh_header_arrow);
            mTvtRehresh = (TextView) mHeaderlayout.findViewById(R.id.refresh_header_state);
            mTvtTime = (TextView) mHeaderlayout.findViewById(R.id.refresh_header_date);
    
            // 写两个0,为测量控件的大小,隐藏刷新部分
            mRefreshView.measure(0, 0);
            // 获取隐藏部分的高度
            mRefreshViewHight = mRefreshView.getMeasuredHeight();
    
            mHeaderlayout.setPadding(0, -mRefreshViewHight, 0, 0);
        }
    

    代码中mHeaderlayout是用户定义的View包括隐藏和显示部分、mRefreshView为刷新的view、mProssBar为刷新部分的进度条、 mArrow刷新时显示上下的箭头 、mTvtRehresh显示刷新时的状态、mTvtTime显示刷新时的时间。

    measure(int widthMeasureSpec, int heightMeasureSpec)
    

    这个方法能够测量出一个视图应该多大。父类容器会相对的约束到它的大小。如果两个参数都设置为0,那么父类就会自动设置它能够允许的最大宽高。

    getMeasuredHeight(); //高度
    

    使用这个方法就可以原始测量出刷新的view的高度了

    mHeaderlayout.setPadding(0, -mRefreshViewHight, 0, 0); //隐藏刷新部分
    

    通过setPadding这个方法可以隐藏刷新的view,mRefreshViewHight是刷新view的显示的高度,设置为-mRefreshViewHight就可以完全隐藏起来。

    3.那么如何来实现下拉的逻辑,现在来看看:

    重写父类的onTouchEvent方法,这个方法是用来响应触摸事件的。触摸事件有三大状态,触摸按下、触摸移动、触摸抬起取消、定义一个常量mCurrentSatae来区分三大状态

    @Override
    public boolean onTouchEvent(MotionEvent ev)
    {
        switch (ev.getAction())
        {
            case MotionEvent.ACTION_DOWN:
                // 获得当前按下的xy坐标
                mDownX = ev.getX();
                mDownY = ev.getY();
            case MotionEvent.ACTION_MOVE:
                float moveX = ev.getX();
                float moveY = ev.getX();
    
                // 在屏幕上移动的距离
                float diffX = moveX - mDownX;
                float diffY = moveY - mDownY;
                
        // 如果正在刷新,自己不响应,交给listview响应
                if (mCurrentSatae == STATE_REFRESH)
                {   
                    break; //退出当前事件
    
                }
    
                // 判断当前页面是否是listview可见的第一个
                if (getFirstVisiblePosition() == 0 && diffY > 0)
                {
    
                    // 给头布局设置paddingTop
                    int hiddenHeight = (int) (mRefreshViewHight - diffY + 0.5f);
                //设置隐藏的高度,就是刷新view随着他不断变化
                    mHeaderlayout.setPadding(0, -hiddenHeight, 0, 0);
    
                    // diffX<mRefreshViewHight :下拉刷新
                    if (diffY < mRefreshViewHight && mCurrentSatae == STATE_RELEASE_REFRESH)
                    {
                        // 更新状态
                        mCurrentSatae = STATE_PULL_DOWN_REFRESH;
    
                        // 更新UI
                        refreshUI();
                        Log.i(TAG, "---下拉刷新");
    
                    }
                    else if (diffY >= mRefreshViewHight && mCurrentSatae == STATE_PULL_DOWN_REFRESH)
                    {
                        // 更新状态
                        mCurrentSatae = STATE_RELEASE_REFRESH;
    
                        // 更新UI
                        refreshUI();
                        Log.i(TAG, "---释放刷新");
                    }
                    // 自己响应touch
                    return true;
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                mDownY = 0;
    
                // 释放后刷新
                if (mCurrentSatae == STATE_PULL_DOWN_REFRESH)
                {
                    // 如果是下拉刷新状态,直接缩回去,也就是隐藏
                    mHeaderlayout.setPadding(0, -mRefreshViewHight, 0, 0);
    
                }
                else if (mCurrentSatae == STATE_RELEASE_REFRESH)
                {
                    // 如果是释放刷新状态,用户希望去刷新数据----正在涮新数据
                    mCurrentSatae = STATE_REFRESH;
                    // 设置paddingtop为0
                    mHeaderlayout.setPadding(0, 0, 0, 0);
    
                    // 更新UI
                    refreshUI();
    
                    //TODO  这里就可以真正去刷新数据了
                }
    
            default:
                break;
        }
        return super.onTouchEvent(ev);
    
    }
    

    完成了刷新三大状态的区分,那在转台改变的时候UI也要随着状态的改变而改变,更新UI的方法如下,通过改变箭头、文字状态,刷新事件、进度条来表示不同状态:mCurrentSatae为当前状态,改变它的直来控制状态的改变。

    // 更新UI
    public void refreshUI()
    {
        switch (mCurrentSatae)
        {
            case STATE_PULL_DOWN_REFRESH:// 下拉刷新
                // 1、:箭头显示、进度条要隐藏
                mArrow.setVisibility(View.VISIBLE);
                mProssBar.setVisibility(View.GONE);
                // 2、状态显示
    
                mTvtRehresh.setText("下拉刷新");
    
                // 3、箭头动画
                mArrow.startAnimation(mUpDownAnimation);
                break;
            case STATE_RELEASE_REFRESH:// 释放刷新
                // 1、:箭头显示、进度条要隐藏
                mArrow.setVisibility(View.VISIBLE);
                mProssBar.setVisibility(View.GONE);
                // 2、状态显示
    
                mTvtRehresh.setText("释放刷新");
    
                // 3、箭头动画
                mArrow.startAnimation(mDownUpAnimation);
                break;
            case STATE_REFRESH:// 正在刷新
                mArrow.clearAnimation();
                // 1、:箭头隐藏、进度条要显示
                mArrow.setVisibility(View.GONE);
                mProssBar.setVisibility(View.VISIBLE);
                // 2、状态显示
                mTvtRehresh.setText("正在刷新");
    
                break;
            default:
                break;
        }
    }
    

    4、现在刷新状态有了,那么什么时候刷新才能真正完成,最后隐藏刷新部分呢,暂且来看:

    这里要加载数据,只有加载数据完成之后才能刷新完成,那就要用到进程间的通讯了,因为加载数据一般在另外一个类中。在我们定义刷新listview中定义一个接口,并且暴露一个方法。且看:

    // 暴露一个方法,刷新完成
    public void setOnRefreshListener(OnRefreshListener listener)
    {
        this.mRefreshListener = listener;
    }
    
    // 定义一个接口,里面定义回调方法
    public interface OnRefreshListener
    {
        /**
         * 正在刷新的回调
         */
        void onRefreshing();
        
    }
    

    现在就来实现刷新完成的逻辑:

    /**
     * 刷新完成收起刷新页面,状态重置
     */
    public void refreshFinish()
    {
        if(isLoading){   //这里是上拉刷新完成的判断,先不用管,看下面
            //隐藏   上拉刷新
            mFootLayout.setPadding(0, -mFootHeight, 0, 0);
            isLoading=false;
            
        }else{//下拉加载
            // 设置当前更新的时间
            mTvtTime.setText(getCurrentTime());
            Log.i(TAG, "刷新完成------");
            // 隐藏 刷新的view
            mHeaderlayout.setPadding(0, -mRefreshViewHight, 0, 0);
    
            // 状态重置
            mCurrentSatae = STATE_PULL_DOWN_REFRESH;
    
            // UI更新
            refreshUI();
        }
    }
    
    }
    
    /**
     * 获取当前的时间
     */
    public String getCurrentTime()
    {
        long time = System.currentTimeMillis();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss");
        return sdf.format(new Date(time));
    
    }
    

    当另一个类调用这个回调方法时就会刷新完成,那么需要在上面Touch触摸事件中的抬起或取消时添加几句,并定义一个刷新完成的监听

    private OnRefreshListener   mRefreshListener;   // 刷新完成的监听                              
    
                //TODO 真正刷新数据
                    // 通知调用者现在处于刷新状态,去网络获取数据
                    // 两个类之间的通讯,回调方法
                    if (mRefreshListener != null)
                    {
                        mRefreshListener.onRefreshing();
                    }
    

    在另一个类中要实现接口:OnRefreshListener 重写onRefreshing()这个方法。就在这个方法中真正刷新数据。加载数据完成之后,告示listView去收起刷新 调用这个方法:mListView.refreshFinish();

    5、看到这里怎么能少了上拉加载呢。上拉加载数据,加载完成显示数据,收起刷新view

    在构造函数中定义一个加载更多的方法

    //加载更多布局
        initFootLayout();
    

    其实加载更多刷新的布局:load_listview_more.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >
    
        <!-- listview的加载更多部分部分 -->
    
        <LinearLayout
                android:id="@+id/refresh_load_part"
            android:layout_width="match_parent"
            android:gravity="center"
            android:orientation="horizontal"
            android:layout_height="100dp" >
    
                <ProgressBar
                     android:id="@+id/refresh_load_pb"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center" />
    
        
                <TextView
                     android:id="@+id/refresh_load_state"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    android:text="正在加载更多..."
                    android:textColor="#ff0000"
                    android:textSize="18sp" />
    
        </LinearLayout>
    
    </LinearLayout>
    

    那现在就来实现上拉加载的方法:

    private void initFootLayout( )
    {
        mFootLayout=View.inflate(getContext(), R.layout.load_listview_more, null);
        //添加到listview的footerview中
        this.addFooterView(mFootLayout);
        //隐藏footlayout布局
        mFootLayout.measure(0, 0);
        mFootHeight = mFootLayout.getMeasuredHeight();
        mFootLayout.setPadding(0, -mFootHeight, 0, 0);
        
        //设置当滑动时加载更多数据,设置监听
        this.setOnScrollListener(this);
    }
    

    在这里先隐藏上拉刷新的view,设置一个setOnScrollListener(this)滚动监听。具体实现什么时候隐藏,什么时候刷新。
    在这里有添加一个接口回调的方法:加载更多数据的方法,让调用者去实现

    // 定义一个接口,里面定义回调方法
    public interface OnRefreshListener
    {
        /**
         * 正在刷新时的回调
         */
        void onRefreshing();
        /**
         * 加载更多的回调
         */
        void loadingMore();
    }
    

    实现滚动监听接口并实现接口里面的两个方法:

    /**
     * 滚动状态改变的时候调用
     */
    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState)
    {
        int lastPosition=getLastVisiblePosition();
        if(lastPosition==getAdapter().getCount()-1){
            if(scrollState==OnScrollListener.SCROLL_STATE_IDLE ||
                    scrollState==OnScrollListener.SCROLL_STATE_TOUCH_SCROLL){
                if(!isLoading){
                    //  滑动到底部显示加载更多     
                    //UI跟新
                    mFootLayout.setPadding(0, 0, 0, 0);
                    Log.i(TAG, "--------------更新UI");
                    //设置自动默认选中i
                    setSelection(getAdapter().getCount());
                    //状态改变
                    isLoading=true;
                    
                    //通知状态变化
                    if (mRefreshListener != null)
                    {
                        Log.i(TAG, "------加载数据----");
    
                //加载网络数据
                        mRefreshListener.loadingMore();
                    }
                    
                }
        
            }
        }
        
        
    }
    
    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
    {
        
    }
    

    这句 mRefreshListener.loadingMore();是调用者实现的方法。在调用者实现的方法里面,在这里加载数据完成之后,把数据加到之前数据的list集合里面,通知适配器刷新listView更新数据,并让listView调用刷新完成,隐藏加载更多的view。

        //给adapter刷新
        listviewAdapter.notifyDataSetChanged();
        
        //刷新完成,告示listView去收起刷新
        mListView.refreshFinish();

    相关文章

      网友评论

          本文标题:Android之轮播图与下拉、上拉刷新

          本文链接:https://www.haomeiwen.com/subject/mywlvttx.html