700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > android 自定义控件之下拉刷新源码详解

android 自定义控件之下拉刷新源码详解

时间:2019-07-14 15:01:54

相关推荐

android 自定义控件之下拉刷新源码详解

下拉刷新 是请求网络数据中经常会用的一种功能.实现步骤如下:1.新建项目 PullToRefreshDemo,定义下拉显示的头部布局pull_to_refresh_refresh.xml <?xmlversion="1.0"encoding="utf-8"?> <RelativeLayoutxmlns:android="/apk/res/android" xmlns:tools="/tools" android:layout_width="match_parent" android:id="@+id/pull_to_refresh_head" android:layout_height="60dip" > <LinearLayout android:layout_width="200dip" android:layout_height="60dip" android:layout_centerInParent="true" android:orientation="horizontal" > <RelativeLayout android:layout_width="0dip" android:layout_height="60dip" android:layout_weight="3" > <ImageView android:id="@+id/iv_arrow" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:src="@drawable/arrow" /> <ProgressBar android:id="@+id/pb" android:layout_width="30dip" android:layout_height="30dip" android:layout_centerInParent="true" android:visibility="gone" /> </RelativeLayout> <LinearLayout android:layout_width="0dip" android:layout_height="60dip" android:layout_weight="12" android:orientation="vertical" > <TextView android:id="@+id/tv_description" android:layout_width="fill_parent" android:layout_height="0dip" android:layout_weight="1" android:gravity="center_horizontal|bottom" android:text="下拉可以刷新" /> <TextView android:id="@+id/tv_update" android:layout_width="fill_parent" android:layout_height="0dip" android:layout_weight="1" android:gravity="center_horizontal|top" android:text="上次更新于%1$s前" /> </LinearLayout> </LinearLayout> </RelativeLayout>2.新建一个RefreshView继承自LinearLayout. publicclassRefreshViewextendsLinearLayoutimplementsOnTouchListener{//下拉状态 publicstaticfinalintSTATUS_PULL_TO_REFRESH=0; //释放立即刷新状态 publicstaticfinalintSTATUS_RELEASE_TO_REFRESH=1; //正在刷新状态 publicstaticfinalintSTATUS_REFRESHING=2; //刷新完成或未刷新状态 publicstaticfinalintSTATUS_REFRESH_FINISH=3; //下拉时头部回滚的速度 publicstaticfinalintSCROLL_SPEED=-20; //一分钟的毫秒值,判断上次的更新时间 publicstaticfinallongONE_MINUTE=60*1000; //一小时的毫秒值,用于判断上次的更新时间 publicstaticfinallongONE_HOUR=60*ONE_MINUTE; //一天的毫秒值 publicstaticfinallongONE_DAY=24*ONE_HOUR; //一月的毫秒值 publicstaticfinallongONE_MONTH=30*ONE_DAY; //一年的毫秒值 publicstaticfinallongONE_YEAR=12*ONE_MONTH; //上次更新时间的字符串常量,用来做SharedPreference的键值 publicstaticfinalStringUPDATE_AT="update_at"; //存储上次更新时间 privateSharedPreferencesmShared; //下拉时显示的View privateViewheader; //下拉刷新的ListView privateListViewlv; //刷新时显示的进度条 privateProgressBarmProgressBar; //指示下拉和释放的箭头 privateImageViewarrow; //指示下拉和释放的文字描述 privateTextViewtv_des; //上次更新时间的文字描述 privateTextViewtv_update; //下拉头的布局参数 privateMarginLayoutParamsheaderLayoutParams; //上次更新时间的毫秒数 privatelonglastUpdateTime; //为了防止不同界面的下拉刷新与上次更新时间互相有冲突,使用id来做区分 privateintmId=-1; //下拉头的高度 privateinthideHeaderHeight; //标志当前是什么状态 privateintcurrentStatus=STATUS_REFRESH_FINISH; //记录上次的状态是什么,避免进行重复操作 privateintlastStatus=currentStatus; //手指按下时的屏幕纵坐标 privatefloatyDown; //在被判断为滚动之前用户手指可以移动的最大值 privateinttouchSlop; //判断已加载过一次layout,这里的onLayout的初始化只需加载一次 privatebooleanloadOnce; //当前是否可以下拉,只有ListView滚到头才允许下拉 privatebooleanableToPull; //下拉刷新的回调接口 privatePullToRefreshListenermListener; publicRefreshView(Contextcontext,AttributeSetattrs){super(context,attrs); mShared=PreferenceManager.getDefaultSharedPreferences(context); header=LayoutInflater.from(context).inflate(R.layout.pull_to_refresh,null,true); mProgressBar=(ProgressBar)header.findViewById(R.id.pb); arrow=(ImageView)header.findViewById(R.id.iv_arrow); tv_des=(TextView)header.findViewById(R.id.tv_description); tv_update=(TextView)header.findViewById(R.id.tv_update); touchSlop=ViewConfiguration.get(context).getScaledTouchSlop()*3;//得到至少移动的距离 refreshUpdatedAtValue();//更新文字描述 setOrientation(VERTICAL);//设置摆放方向 addView(header,0); } //进行一些关键的初始化操作,比如:将下拉头向上偏移进行隐藏,给ListView注册touch事件 @Override protectedvoidonLayout(booleanchanged,intl,intt,intr,intb){super.onLayout(changed,l,t,r,b); if(changed&&!loadOnce){//只执行一次 hideHeaderHeight=-header.getHeight();//设置成负值刚好隐藏在页面的最上方 headerLayoutParams=(MarginLayoutParams)header.getLayoutParams(); headerLayoutParams.topMargin=hideHeaderHeight;//设置布局的topMargin header.setLayoutParams(headerLayoutParams); lv=(ListView)getChildAt(1);//找到listView因为第一个Child是上拉头所以第二个才是ListView. lv.setOnTouchListener(this); loadOnce=true; } } //给下拉刷新控件注册一个监听器 publicvoidsetOnRefreshListener(PullToRefreshListenermListener,intid){this.mListener=mListener; mId=id; } //更新下拉头中的信息 privatevoidupdateHeaderView(){if(lastStatus!=currentStatus){if(currentStatus==STATUS_PULL_TO_REFRESH){tv_des.setText("下拉刷新"); arrow.setVisibility(View.VISIBLE); mProgressBar.setVisibility(View.GONE); rotateArrow(); } elseif(currentStatus==STATUS_RELEASE_TO_REFRESH){tv_des.setText("释放刷新"); arrow.setVisibility(View.VISIBLE); mProgressBar.setVisibility(View.GONE); rotateArrow(); } elseif(currentStatus==STATUS_REFRESHING){tv_des.setText("正在刷新中"); mProgressBar.setVisibility(View.VISIBLE); arrow.clearAnimation();//清除动画效果 arrow.setVisibility(View.GONE); } refreshUpdatedAtValue(); } } //根据当前的状态来旋转箭头 privatevoidrotateArrow(){floatpivoX=arrow.getWidth()/2f; floatpivoY=arrow.getHeight()/2f; floatfromDegress=0f; floattoDegress=0f; if(currentStatus==STATUS_PULL_TO_REFRESH){fromDegress=180f; toDegress=360f; } else{fromDegress=0f; toDegress=180f; } RotateAnimationanimation=newRotateAnimation(fromDegress,toDegress,pivoX,pivoY); animation.setDuration(100); animation.setFillAfter(true); arrow.startAnimation(animation); } //根据当前listView的滚动状态来设定ableToPull的值 //每次都需要在onTouch中的一个执行,这样可以判断出当前滚动的是listView,还是应该进行下拉 privatevoidsetIsAbleToPull(MotionEventevent){ViewfirstView=lv.getChildAt(0); if(firstView!=null){intfirstVisiblePos=lv.getFirstVisiblePosition();//获得listView顶头项的是该列数据的第几个 if(firstVisiblePos==0&&firstView.getTop()==0){if(!ableToPull){yDown=event.getRawY(); } //如果首个元素的上边缘,距离父布局值为0,就说明listView滚到了最顶部,此时允许下拉刷新 ableToPull=true; } else{if(headerLayoutParams.topMargin!=hideHeaderHeight){headerLayoutParams.topMargin=hideHeaderHeight; header.setLayoutParams(headerLayoutParams); } ableToPull=false; } } } //当所有刷新的逻辑执行完成后,停止刷新,并记录 publicvoidfinishRefreshing(){currentStatus=STATUS_REFRESH_FINISH; mShared.edit().putLong(UPDATE_AT+mId,System.currentTimeMillis()).commit(); newHideHeaderTask().execute(); } //更新下拉头中上次更新时间的文字描述 privatevoidrefreshUpdatedAtValue(){lastUpdateTime=mShared.getLong(UPDATE_AT+mId,-1);//从配置文件中取出上次更新的时间的毫秒数 longcurrentTime=System.currentTimeMillis();//获得当前时间毫秒数 longtimePassed=currentTime-lastUpdateTime;//中间相差的毫秒数 longtimeIntoFormat; StringupdateAtValue; if(lastUpdateTime==-1){updateAtValue="暂未更新过"; } elseif(timePassed<0){updateAtValue="时间故障"; } elseif(timePassed<ONE_MINUTE){updateAtValue="刚刚更新"; } elseif(timePassed<ONE_HOUR){timeIntoFormat=timePassed/ONE_HOUR; Stringvalue=timeIntoFormat+"分钟"; updateAtValue=String.format("上次更新于%1$s前",value); } elseif(timePassed<ONE_DAY){timeIntoFormat=timePassed/ONE_HOUR; Stringvalue=timeIntoFormat+"小时"; updateAtValue=String.format("上次更新于%1$s前",value); } elseif(timePassed<ONE_MONTH){timeIntoFormat=timePassed/ONE_DAY; Stringvalue=timeIntoFormat+"天"; updateAtValue=String.format("上次更新于%1$s前",value); } elseif(timePassed<ONE_YEAR){timeIntoFormat=timePassed/ONE_MONTH; Stringvalue=timeIntoFormat+"月"; updateAtValue=String.format("上次更新于%1$s前",value); } else{timeIntoFormat=timePassed/ONE_YEAR; Stringvalue=timeIntoFormat+"年"; updateAtValue=String.format("上次更新于%1$s前",value); } tv_update.setText(updateAtValue); } //当listView被触摸时调用,其中处理了各种下拉刷新的具体逻辑 @Override publicbooleanonTouch(Viewv,MotionEventevent){setIsAbleToPull(event); if(ableToPull){switch(event.getAction()){caseMotionEvent.ACTION_DOWN: yDown=event.getRawY(); break; caseMotionEvent.ACTION_MOVE: floatyMove=event.getRawY(); intdistance=(int)(yMove-yDown); if(distance<=0&&headerLayoutParams.topMargin<=hideHeaderHeight){returnfalse; } if(distance<touchSlop){returnfalse; } if(currentStatus!=STATUS_REFRESHING){if(headerLayoutParams.topMargin>0){currentStatus=STATUS_RELEASE_TO_REFRESH; } else{currentStatus=STATUS_PULL_TO_REFRESH; } headerLayoutParams.topMargin=(distance/2)+hideHeaderHeight; header.setLayoutParams(headerLayoutParams);//让ListView可以弹动 } break; caseMotionEvent.ACTION_UP: default: if(currentStatus==STATUS_RELEASE_TO_REFRESH){//松开手如果是释放立即刷新,则去调用刷新的任务 newRefreshingTask().execute(); } elseif(currentStatus==STATUS_PULL_TO_REFRESH){//松开手如果是下拉状态,则去隐藏下拉头的任务 newHideHeaderTask().execute(); } break; } if(currentStatus==STATUS_PULL_TO_REFRESH||currentStatus==STATUS_RELEASE_TO_REFRESH){updateHeaderView(); //当前处于下拉或释放状态,要让listView失去焦点,否则被点击的那一项会一直处于选中状态 lv.setPressed(false); lv.setFocusable(false); lv.setFocusableInTouchMode(false); lastStatus=currentStatus; returntrue; } } returnfalse; } //正在刷新的任务 classRefreshingTaskextendsAsyncTask<Void,Integer,Void>{@Override protectedVoiddoInBackground(Void...params){inttopMargin=headerLayoutParams.topMargin; while(true){topMargin=topMargin+SCROLL_SPEED; if(topMargin<=0){topMargin=0; break; } publishProgress(topMargin); sleep(10); } currentStatus=STATUS_REFRESHING; publishProgress(0); if(mListener!=null){mListener.onRefresh();//通知刷新 } returnnull; } @Override protectedvoidonProgressUpdate(Integer...topMargin){updateHeaderView(); headerLayoutParams.topMargin=topMargin[0]; header.setLayoutParams(headerLayoutParams); } } //隐藏下拉头的任务 classHideHeaderTaskextendsAsyncTask<Void,Integer,Integer>{@Override protectedIntegerdoInBackground(Void...params){inttopMargin=headerLayoutParams.topMargin; while(true){topMargin=topMargin+SCROLL_SPEED;//慢慢往回收缩 if(topMargin<=hideHeaderHeight){//判断是不是回到了原位 topMargin=hideHeaderHeight; break; } publishProgress(topMargin);//设置收缩动作 sleep(10); } returntopMargin; } @Override protectedvoidonProgressUpdate(Integer...values){headerLayoutParams.topMargin=values[0]; header.setLayoutParams(headerLayoutParams); } @Override protectedvoidonPostExecute(Integerresult){headerLayoutParams.topMargin=result; header.setLayoutParams(headerLayoutParams); currentStatus=STATUS_REFRESH_FINISH; } } /** *使当前线程睡眠指定的毫秒数。 * *@paramtime *指定当前线程睡眠多久,以毫秒为单位 */ privatevoidsleep(inttime){ try{ Thread.sleep(time); }catch(InterruptedExceptione){ e.printStackTrace(); } } //下拉刷新的监听器 publicinterfacePullToRefreshListener{voidonRefresh(); } }首先,在构造函数中动态添加了pull_to_refresh这个布局作为下拉头,然后将onLayout方法中将下拉头向上偏移出了屏幕,再给ListView注册了Touch事件.如果在ListView上进行滑动,onTouch就会执行,onTouch首先会用setIsAbleToPull方法判断ListView是否滚动到了最顶部,只有滚动到最顶部才会执行后面的代码,否则就是ListView的正常滚动,不作处理.当ListView滚动到最顶部,如果手指还在向下拖动,就会改变下拉头的偏移值,让下拉头显示出来,如果下拉的距离足够大,在松手后就会执行刷新操作,如果距离不够大,则会隐藏下拉头.

具体刷新方法操作在RefreshingTask中进行,其中在doInBackground方法中回调了PullToRefreshListener接口的onRefresh()方法.具体使用方法如下:3.在activity_main.xml中 <RelativeLayoutxmlns:android="/apk/res/android" xmlns:tools="/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <com.cy.pulltorefreshDemo.RefreshView android:id="@+id/refresh_view" android:layout_width="fill_parent" android:layout_height="fill_parent" > <ListView android:id="@+id/lv" android:layout_width="fill_parent" android:layout_height="fill_parent" android:cacheColorHint="@android:color/transparent" ></ListView> </com.cy.pulltorefreshDemo.RefreshView> </RelativeLayout>只要将需要刷新的ListView包含在 RefreshView中.

4.MainActivity.javapublicclassMainActivityextendsActivity{RefreshViewrefreshView; ListViewlv; ArrayAdapter<String>adapter; List<String>items=newArrayList<String>(); @Override protectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); items.add("A"); items.add("B"); refreshView=(RefreshView)findViewById(R.id.refresh_view); lv=(ListView)findViewById(R.id.lv); adapter=newArrayAdapter<String>(this,android.R.layout.simple_list_item_1,items); lv.setAdapter(adapter); refreshView.setOnRefreshListener(newPullToRefreshListener(){@Override publicvoidonRefresh(){try{Thread.sleep(3000); }catch(InterruptedExceptione){e.printStackTrace(); } items.add("222");//自动添加到ListView中 refreshView.finishRefreshing(); } },0); } }就这样,一个完整的下拉刷新.

来自为知笔记(Wiz)

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。