轮播图与下拉、上拉刷新
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位的01010101010101010
- MeasureSpec :
- mode:
- EXACTLY 30dp
- AT_MOST 100dp
- UNSPECIFIED
- size: 实际大小
- mode:
- measure: 测量控件的宽度和高度(widthMeasureSpec,heightMeasureSpec)
-
刷新状态的介绍:
- 需要下拉刷新
- 释放刷新
- 正在刷新
现在来一步一步解析:
下拉刷新的原理就是在一个布局中定义了刷新的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();
网友评论