亲自动手编写Android通用刷新控件
项目中我们经常有上拉、下拉刷新的需求,几乎所有的listView、RecyclerView都会伴随着上拉、下拉刷新的需求,如果我们使用一些开源控件,换了控件我们就要更新,现在我们自己撸起袖子写一个通用的刷新控件
项目地址:https://git.oschina.net/qiangshen/commentview.git
思路:
- 写一个继承RelativeLayout的RefreshLayout
- 添加头尾控件作为刷新控件
- 通过事件分发来进行刷新操作
- 通过动画来控制控件移动
目的:让他的所有子控件都可以使用,哪怕是一个TextView
publicclassRefreshLayoutextendsRelativeLayout{
/**
*滑动控件时拉去的速度比例
*/
privatefinalintV_REFRESH=2;
/**
*是否是刷新过程
*true是
*false不是
*为false的时候才可以进行刷新
*/
privatebooleanmIsRefreshDuring;
/**
*可以进下拉刷新
*/
privatebooleanmCanDownPull;
/**
*可以进行上拉刷新
*/
privatebooleanmCanUpPull;
/**
*判断触摸后是否是初次移动
*/
privatebooleanmIsFirstMove;
/**
*y轴呢平移的距离
*/
privateintmDistanceY;
/**
*刷新接口对象
*/
privateOnRefreshmOnRefresh;
/**
*用于控制事件拦截的变量
*/
privatebooleanmCanIntercept;
privateintmTouchSlop;
privateintmDistance;
privateLayoutParamsmHeaderParams;
privateViewmHeaderView;
privateViewmFootView;
privateintmHeaderMaxHeight;
privateintmStartY;
privateLayoutParamsmFootParams;
privateintmFootMaxHeight;
privatePullCallBackmCallBack;
privateViewmChildView;
privateObjectAnimatormAnimator;
publicRefreshLayout(Contextcontext){
super(context);
initData();
}
publicRefreshLayout(Contextcontext,AttributeSetattrs){
super(context,attrs);
initData();
}
publicRefreshLayout(Contextcontext,AttributeSetattrs,intdefStyleAttr){
super(context,attrs,defStyleAttr);
initData();
}
/**
*必须让头尾控件实现的接口
*/
publicinterfaceHeadAndFootCallBack{
//设置属性
voidsetAttribute();
//开始刷新
voidstartPull();
//停止刷新
voidstopPull();
}
/**
*必须让被拖动的控件子类实现
*/
publicinterfacePullCallBack{
booleancanDownPull();
booleancanUpPull();
}
privatevoidinitData(){
//不调用该方法不能进行绘制
setWillNotDraw(false);
}
/**
*下拉刷新完成后必须使用该方法
*/
publicvoiddownPullFinish(){
mAnimator.setFloatValues(mChildView.getTranslationY(),0);
mAnimator.start();
((HeadAndFootCallBack)mHeaderView).stopPull();
}
/**
*上拉完成后必须调用该方法
*/
publicvoidupPullFinish(){
mAnimator.setFloatValues(mChildView.getTranslationY(),0);
mAnimator.start();
((HeadAndFootCallBack)mFootView).stopPull();
}
/**
*自动下拉刷新
*/
publicvoidautoDownPullForHead(){
postDelayed(newRunnable(){
@Override
publicvoidrun(){
mCanDownPull=true;
mCanUpPull=false;
mAnimator.setFloatValues(10,mHeaderMaxHeight);
mAnimator.start();
((HeadAndFootCallBack)mHeaderView).startPull();
mOnRefresh.onDownPullRefresh();
}
},500);
}
/**
*自动下拉刷新
*/
publicvoidautoUpPullForHead(){
postDelayed(newRunnable(){
@Override
publicvoidrun(){
mCanDownPull=false;
mCanUpPull=true;
mAnimator.setFloatValues(0,mFootMaxHeight);
mAnimator.start();
((HeadAndFootCallBack)mFootView).startPull();
mOnRefresh.onUpPullRefresh();
}
},500);
}
@Override
publicbooleanonInterceptTouchEvent(MotionEventev){
returnmCanIntercept;
}
@Override
publicbooleanonTouchEvent(MotionEventevent){
returntrue;
}
@Override
publicbooleandispatchTouchEvent(MotionEventevent){
Log.e("shen","mIsRefreshDuring="+mIsRefreshDuring);
if(mIsRefreshDuring)
/**如果正在进行刷新将不会获取MotionEvent*/
{
returnsuper.dispatchTouchEvent(event);
}
switch(event.getAction()){
caseMotionEvent.ACTION_DOWN:
mStartY=(int)event.getY();
initPull();
break;
caseMotionEvent.ACTION_MOVE:
if(event.getPointerCount()==1){
intmoveY=(int)event.getY();
mDistanceY=(moveY-mStartY)/V_REFRESH;
if(!mIsFirstMove&&mDistanceY!=0&&mDistanceY0;
mCanUpPull=!mCanDownPull;
mIsFirstMove=true;
}
if(mCanDownPull&&mCallBack.canDownPull()){
upDataForDownPull();//下拉刷新
mChildView.setEnabled(false);
mCanIntercept=true;
}
if(mCanUpPull&&mCallBack.canUpPull()){
upDataForUpPull();//上拉加载
mChildView.setEnabled(false);
mCanIntercept=true;
}
mStartY=moveY;
}
break;
caseMotionEvent.ACTION_UP:
mIsRefreshDuring=true;
mIsFirstMove=false;
if(mHeaderParams.height>=mHeaderMaxHeight)
/**可以下拉刷新**/
{
((HeadAndFootCallBack)mHeaderView).startPull();
mOnRefresh.onDownPullRefresh();
}elseif(mFootParams.height>=mFootMaxHeight)
/**可以上拉刷新**/
{
((HeadAndFootCallBack)mFootView).startPull();
mOnRefresh.onUpPullRefresh();
}elseif(mHeaderParams.height>0&&mHeaderParams.height0&&mFootParams.height=mFootMaxHeight){
mFootParams.height=mFootMaxHeight;
}
mChildView.setTranslationY(-mFootParams.height);
mFootView.requestLayout();
}
}
/**
*下拉时处理手势
*/
privatevoidupDataForDownPull(){
if(mDistanceY!=0){
mHeaderParams.height+=mDistanceY;
if(mHeaderParams.height>=mHeaderMaxHeight){//最大
mHeaderParams.height=mHeaderMaxHeight;
}
if(mHeaderParams.height<=0){//最小
mHeaderParams.height=0;
}
mChildView.setTranslationY(mHeaderParams.height);
mHeaderView.requestLayout();
}
}
@Override
protectedvoidonAttachedToWindow(){
super.onAttachedToWindow();
}
@Override
protectedvoidonFinishInflate(){
super.onFinishInflate();
//加载头
mHeaderView=getChildAt(0);
if(!(mHeaderViewinstanceofHeadAndFootCallBack)){
newIllegalStateException("HeaderView必须实现HeadAndFootCallBack接口");
}
((HeadAndFootCallBack)mHeaderView).setAttribute();
mHeaderParams=(LayoutParams)mHeaderView.getLayoutParams();
mHeaderParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);
//加载尾
mFootView=getChildAt(2);
if(!(mFootViewinstanceofHeadAndFootCallBack)){
newIllegalStateException("FootView必须实现HeadAndFootCallBack接口");
}
((HeadAndFootCallBack)mFootView).setAttribute();
mFootParams=(LayoutParams)mFootView.getLayoutParams();
mFootParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
mChildView=getChildAt(1);
if(!(mChildViewinstanceofHeadAndFootCallBack)){
newIllegalStateException("ChildView必须实现PullCallBack接口");
}
mCallBack=(PullCallBack)getChildAt(1);
//设置动画
mAnimator=ObjectAnimator.ofFloat(mChildView,"translationY",0);
mAnimator.setInterpolator(newDecelerateInterpolator());
mAnimator.addUpdateListener(newValueAnimator.AnimatorUpdateListener(){
@Override
publicvoidonAnimationUpdate(ValueAnimatoranimation){
inttranslationY=(int)mChildView.getTranslationY();
if(mCanUpPull){//从移动到的位置往下滑
mFootParams.height=Math.abs(translationY);
mFootView.requestLayout();
}elseif(mCanDownPull){
mHeaderParams.height=Math.abs(translationY);
mHeaderView.requestLayout();
}
Log.e("shen","translationY="+translationY);
Log.e("shen","mHeaderParams.height="+mHeaderParams.height);
if(translationY==0){
mChildView.setEnabled(true);
mDistanceY=0;//重置
mIsRefreshDuring=false;//重置
mCanIntercept=false;
}else{
mIsRefreshDuring=true;
}
}
});
}
@Override
protectedvoidonSizeChanged(intw,inth,intoldw,intoldh){
super.onSizeChanged(w,h,oldw,oldh);
mTouchSlop=ViewConfiguration.get(getContext()).getScaledTouchSlop();
mDistance=mTouchSlop*5;
//设置下拉头初始属性
mHeaderMaxHeight=mHeaderParams.height;
mHeaderParams.height=0;
mHeaderView.requestLayout();
//设置上拉尾初始属性
mFootMaxHeight=mFootParams.height;
mFootParams.height=0;
mFootView.requestLayout();
}
/**
*下拉/上拉事件监听
*/
publicinterfaceOnRefresh{
/**
*下拉刷新
*/
voidonDownPullRefresh();
/**
*上拉加载
*/
voidonUpPullRefresh();
}
publicvoidsetOnRefresh(OnRefreshonRefresh){
mOnRefresh=onRefresh;
}
}
给他添加三个控件,头尾就是刷新头、尾,第二个就是正常显示的控件。必须让头尾实现HeadAndFootCallBack接口,来设置属性,通知开始刷新、结束刷新
难点:现在来说下开发时遇到的难点
- 由于判断在dispatchTouchEvent中,导致如果该控件以及子控件都不消费该事件的话,就会造成事件不会发送到它,因为如果不消费DOWN事件的话,之后所有的事件都不会在进行接收。解决方式,让该控件onTouchEvent方法消返回true,当子控件不进行事件消费的话,就会返回由该控件消费,不会造成因DOWN事件不消费而无法接收到事件,导致dispatchTouchEvent也不消费事件
- 动画,动画就是我的伤痛,最近在学习估值器
这个控件自认为写的不错,通过他可以帮我们学习事件分发、动画、接口回调,也是有一定的学习意义
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。