Android智慧北京之轮播图与下拉、上拉刷新

    xiaoxiao2025-03-14  10

    轮播图与下拉、上拉刷新

    Handler

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

    轮播图的实现

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

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

    重写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(),当页面改变时触发这个方法。

    下拉刷新

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

    控件的测量:

    measure: 测量控件的宽度和高度(widthMeasureSpec,heightMeasureSpec) 32位的01010101010101010MeasureSpec : mode: EXACTLY 30dp AT_MOST 100dpUNSPECIFIED size: 实际大小

    刷新状态的介绍:

    需要下拉刷新释放刷新正在刷新

    现在来一步一步解析:

    下拉刷新的原理就是在一个布局中定义了刷新的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();
    转载请注明原文地址: https://ju.6miu.com/read-1297014.html
    最新回复(0)