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

2016-12-24  本文已影响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();
上一篇下一篇

猜你喜欢

热点阅读