注:所有blog局限于博主水平有限,很多不足之处大家可以指出共同探讨进步。
尊重原创转载请注明:From 倪大叶(http://blog.csdn.net/renyi0109) 侵权必究!

一般自定义控件分成两大类:
1. 测试()将几个已有的控件“拼接”成一个特有的控件,一般用到的知识就是上两篇blog所写的事件分发,测量等等。。 中心思想就是让几个独立的已有控件根据自己特有的属性加上外部控制协调的一起工作
2. 上篇我们最后一带而过的onDraw方法的丰富实现,也就是自己去画出一个特有的控件,这个的重点主要就是画的工具的学习,path,paint,layer等等类的使用以及他们提供的海量api,不仅如此一些较复杂的控件绘制还需要扎实的数学基础,这个基础可不是指初中高中那些小混混,而是在大学以高数带头,手下集结了离散,线数等一帮凶神恶煞小弟的大学第一帮派, 该帮派在大学为非作歹,奸淫掳掠,无恶不作,人神共愤,多少青年俊杰,黄花闺女折在这帮孙子手上。 鉴于我这几年也是依附在寝室一个天生神力,战无不胜的大神麾下才在这帮恶徒手下勉强偷生,所以这方面就不多讨论。但是也别怕 还是有很多这类需求用一点简单的数学知识加逻辑就能画出来,就算碰到复杂的需求还可以去github上找类似的,如果找不到更不用怕了,直接告诉产品实现不了,所以说问题嘛总有解决办法

今天我就结合前两篇的知识写个通用的下拉刷新控件demo,这demo主要是讲解为主,临时花了点时间做的,很多东西没有考虑进去,BUG什么的也没有调,所以并不适合拿来直接用到项目中,主要是学习一些用法而已。过程中主要讲一些关键点,效果图什么的就不贴了,大家可以到我github(https://github.com/renyindy/SupperRefreshVIew)上去下然后跑起来结合本篇blog来看(千万别直接用到项目中,如果出了问题这锅老子可不背,切记!) 废话不多说 上demo

先分析下结构应该是什么样的,首先不能像传统ListView加头部的方法去做这样违背了通用原则要知道这是listView独有的接口,虽然其他View没有但是我们可以模仿这种做法去给所有需要刷新的View加个”头部”以及”底部”, 试想一下 我们有这么一个容器,中间是放我们需要刷新的View,上下分别是刷新头部和底部那问题不就解决了吗? 这结构脑子随便一想就知道用Linearlayout再合适不过

既然是通用我们就不能只在内容上允许随意更改;刷新,加载更多的样式也应该是可替换的,所以我们先设计一下头部和底部的通用接口:

    public abstract class UpdateSuperView extends LinearLayout {public static final int STATE_NORMAL = 0;  //常规状态public static final int STATE_ALREADY = 1;   //已可触发刷新public static final int STATE_REFRESHING = 2;  //正在刷新public static final int STATE_LOADING = 3; //正在加载protected RefreshAndLoadListener mRefreshAndLoadListener;public UpdateSuperView(Context context) {super(context);}public UpdateSuperView(Context context, AttributeSet attrs) {super(context, attrs);}/*** 用来更改头部或底部的可见高度* @param value*/public abstract void updateHeight(int value);/*** 重置头部或底部的可见高度,这个取决于当前state以决定重置为什么高度,不一定是不可见*/public abstract void reseatHeight();/*** 设置当前状态* @param state*/public abstract void setState(int state);/*** 获取当前状态* @return*/public abstract int getState();/*** 获取当前头部或底部的可见高度* @return*/public abstract int getVisableHeight();/*** 上拉加载和下拉刷新触发回调监听*/public interface RefreshAndLoadListener{void onRefresh();void onLoadMore();}protected void setRefeshAndLoarListener(RefreshAndLoadListener  refeshAndLoarListener){this.mRefreshAndLoadListener = refeshAndLoarListener;}}

头部和底部有了,再来弄个内容,这里我们就不能让需要刷新的View去继承一个接口,然后再面向接口设计了,因为你不可能让别人还要让想刷新的View都去继承这个接口接着还得自己实现这个接口中的方法吧??这不是拿人作宝搞吗。。 我们设计应该是这样,用的人只用传一个想刷新的View进来,其他就不用管了。 既然不能面向接口设计,那么我们就用一个Holder来包裹一下需要刷新的View,然后面向这个Holder编程就OK了,看一下Holder设计:

    public class RefreshHolder implements AbsListView.OnScrollListener {//需要刷新的Viewprivate View mChild;//........... public void setContentView(View view) {this.mChild = view;}//.........../*** 达到顶部监听** @return*/public boolean isTop() {if (mChild instanceof AbsListView) { //ListView到达顶部监听AbsListView absListView = (AbsListView) mChild;return !canScrollVertically(mChild, -1)|| absListView.getChildCount() > 0&& (absListView.getFirstVisiblePosition() == 0 &&   absListView.getChildAt(0).getTop() == 0);} else if (mChild instanceof ScrollView) { //ScrollView达到顶部监听ScrollView scrollView = (ScrollView) mChild;return scrollView.getScrollY() == 0;} else {return canScrollVertically(mChild, -1) || mChild.getScrollY() > 0;}}/*** 到达底部监听** @return*/public boolean isBottom() {if (mChild instanceof AbsListView) {//ListView到达底部监听AbsListView absListView = (AbsListView) mChild;return !canScrollVertically(mChild, 1);} else if (mChild instanceof ScrollView) { //ScrollView顶部底部监听ScrollView scrollView = (ScrollView) mChild;View childView = scrollView.getChildAt(0);if (childView != null) {return !canScrollVertically(mChild, 1)|| childView.getMeasuredHeight() <= scrollView.getHeight()     + scrollView.getScrollY();}}return false;}//..............}

这个Holder最关键的就是isTop方法和isBottom方法,我们控件刷新的设计思想就是如果达到顶部继续下拉或者到底部继续上拉就中断事件下发由我们的刷新控件接管事件,并进行头部或者底部的相应处理, 如果不满足这两个控件就将事件传递下去,让内在的View自己去处理。 这里我们只做了ListView和ScrollView的顶部底部监听判断,如果想兼容其他View,比如RecyclerView甚至自定义View就直接在这方面里面添加条件判断即可。

现在我们来看看这个刷新ViewGroup怎么设计,首先先把HeaderView,ContentView(需要刷新的内容View),FooterView依次加入我们的刷新容器中,因为我们的刷新GroupView是一个竖直的Linearlayout,我们只需要将hearView的高度设置为0,ContentView设置为match_parent,FooterView随意给需要的高度就行,这样初始化显示就只有一个ContentView,当我们满足下拉条件的时候依次增加HeaderView的高度,就能让HeadView显示出来达到下拉刷新的效果,而当我们上拉条件满足的时候,我们让FooterView和ContentView一起像上做偏移就能让FooterView显示出来, 可能有人会问了干嘛不用和头部一样的方式先把高度设置为0然后逐渐增大呢? 这么问我只能说你连LinearLayout都没想明白,稍稍动下脑如果用增加高度的方法,那么Linearlayout总共就这么大footerView占用了高度那ContentView是否会被挤压变形呢?如果你又问头部怎么不会被挤压。。。。 那么这个话题我觉得就聊不下去了 。
虽然用偏移的方式是可行的但是我们得改一点Linearlayout的onMeasure逻辑,因为LinearLayout的测量规则是不包含超出显示区域的子View的宽高的,所以我们这里要让Linearlayout根据子View总共有多高我就要设置LinearLayout多高

    /*** 更改LinearLayout测量逻辑,height=childHeight*childCount** @param widthMeasureSpec* @param heightMeasureSpec*/@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);int width = MeasureSpec.getSize(widthMeasureSpec);int childCount = getChildCount();int finalHeight = 0;for (int i = 0; i < childCount; i++) {View child = getChildAt(i);LayoutParams margins = (LayoutParams) child.getLayoutParams();if (child.getVisibility() != View.GONE) {final int childWidthMeasureSpec = final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, width);final int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, 0, margins.height);measureChild(child, childWidthMeasureSpec, childHeightMeasureSpec);finalHeight += child.getMeasuredHeight();}}setMeasuredDimension(width, finalHeight);}  

这里我就简单的依次累加了,并没有考虑margin进去,margin对这个控件有点鸡肋,是不建议给里面的ContentView设置margin的,会影响刷新效果很难看, 万事具备就差最关键的事件分发模块了:

    public class SuperRefreshView extends LinearLayout {@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {switch (ev.getAction()) {case MotionEvent.ACTION_DOWN:isIntercept = false;mLastY = ev.getRawY();mLastX = ev.getRawX();break;case MotionEvent.ACTION_MOVE:mLastMoveEvent = ev;int deltaY = (int) (ev.getRawY() - mLastY);int deltaX = (int) (ev.getRawX() - mLastX);//headView  处理滑动if ((mHeadView.getVisableHeight() > 0 || (deltaY > 0 && mRefreshHolder.isTop())) && isRefresh) {if (!isShowHeader) {isShowHeader = true;}sendCancelEvent();updateHeaderHeight(deltaY);} else if ((mRefreshHolder.getOffsetY() < 0 || deltaY < 0 && mRefreshHolder.isBottom()) && isLoadMore) { //footerView 处理滑动if (!isShowFooter) {isShowFooter = true;}sendCancelEvent();updateFooterHeight(deltaY / 2);}if (mHeadView.getVisableHeight() <= 0 && isShowHeader && deltaY < 0) {isShowHeader = false;sendDownEvent();} else if (mRefreshHolder.getOffsetY() >= 0 && isShowFooter && deltaY > 0) {isShowFooter = false;sendDownEvent();}mLastY = ev.getRawY();break;case MotionEvent.ACTION_UP:if (mHeadView.getVisableHeight() > 0) {reseatHeaderHeight();}if (mRefreshHolder.getOffsetY() != 0) {reseatFooterHeight();}break;}return super.dispatchTouchEvent(ev);}

逻辑很简单就不细讲了,但是有个关键的地方大家应该注意到有两个方法 sendCancelEent()和sendDownEvent(), 这两个方法是干嘛的先暂且不说,我们来看看整个刷新控件中唯一的难点,试想一种情况: 当一开始判定没有满足刷新规则,直接将事件分发下去让子View做处理,这时候达到刷新条件父View需要接管事件自己处理而不让子View处理,好了,肯定有人会说简单啊拦截掉不就行了么,最开始我也这么天真过,拦呗~~, OK问题解决了提交代码上传测试
转天测试过来:XX你这刷新有问题啊! “what? 我的代码有问题?你是认真的吗?”,”真的 你看我拉到刷新这里以后不松手,再慢慢放回去,然后继续往下移动,里面的View不能跟着滑动了”,仿佛一道惊雷打在我天灵盖上, 对啊 我拦截了事件传不下去了,这时候只要不放手里面的内容View就再也不能接收到事件,所以当再滑回头部继续滑动的时候,按道理应该是子View接管事件做自己的滑动处理,可是现在不行了!怎么办 当时这个问题也的确难住了我,拦截是中断形式的,一次拦截终生受用,最后为了赶着上线当这种情况下我根据父View拿到的滑动事件信息去手动的调ListView(当时内部是listView)的滑动方法强行滑动,效果烂不说,还要处理一堆杂事,比如放手后做根据放手时的加速度去模拟listView做惯性滑动等等。。。 事后我决定从根源上找到解决办法而不是这么low的外部辅助方法,我再一次看了事件方面的源码想从里面找到思路,可是中断拦截貌似是铁律除非你去自己写分发逻辑,这种傻逼想法就不说了。。。 当我卡在传统思维死胡同中的时候突然惊醒,我既然能外部模拟ListView滑动去处理,为什么我不能模拟事件给子View呢?为什么非要死板的认为事件中断下发了一定要用户重新按下才能传递下去,一旦想通这一点这问题就迎刃而解了,再看上面的代码当hearView处理滑动的时候我不是拦截事件而是调用了sendCancelEent()方法,我们进去看一下:

    /*** 模拟 cancel 用于分发到 内部子View*/private void sendCancelEvent() {//根据当前move事件模拟一个cancel事件传递给子View,这时候作用其实就是相当于拦截MotionEvent last = mLastMoveEvent;MotionEvent e = MotionEvent.obtain(last.getDownTime(),last.getEventTime()+ ViewConfiguration.getLongPressTimeout(),MotionEvent.ACTION_CANCEL, last.getX(), last.getY(),last.getMetaState());dispatchTouchEventSupper(e);}

接下来子View不会再接收到事件当然就不会再有响应,当发生到上诉所说的情况只需同样的思想再模拟一个down事件传递给子View,那么就可以完美的绕开中断机制实现事件分发桥接

    /*** 模拟 down事件 用于分发到 内部子View*/private void sendDownEvent() {final MotionEvent last = mLastMoveEvent;if (last == null)return;MotionEvent e = MotionEvent.obtain(last.getDownTime(),last.getEventTime(), MotionEvent.ACTION_DOWN, last.getX(),last.getY(), last.getMetaState());dispatchTouchEventSupper(e);}

这小玩意儿差不多就讲完了,这东西大家自己强化强化改吧改吧完全可以用到自己项目中,当然网上已经有很多成熟强大的下拉刷新库,我喜欢自己写纯属因为改起来快,想怎么弄怎么弄,碰到BUG定位也很快,也比较轻。

自定义控件从入门到轻生之---来个结晶相关推荐

  1. 自定义控件从入门到轻生之---初尝禁果

    所有blog局限于博主水平有限,很多不足之处大家可以指出共同探讨进步. 尊重原创转载请注明:From 倪大叶http://blog.csdn.net/renyi0109 侵权必究!虽然我不知道具体怎么 ...

  2. 自定义控件从入门到轻生之---解锁新姿势

    所有blog局限于博主水平有限,很多不足之处大家可以指出共同探讨进步. 尊重原创转载请注明:From 倪大叶http://blog.csdn.net/renyi0109 侵权必究!虽然我不知道具体怎么 ...

  3. ASP.NET 自定义控件从入门到精通3补充

    ASP.NET 自定义控件从入门到精通 3 状态管理和Style类 3.2 新的Render方法 源码下载 首先我们来看看Register控件在前台生成的Html代码,代码如下所示: <!-注意 ...

  4. Android自定义控件开发入门与实战(1)绘图基础

    今天从leader那里拿到了启舰大神写的<自定义控件开发入门与实战>这本书,据说看完了,至少写起自定义view也不会慌. 最重要的是多练,所以这本书基本设计到的我没有涉及过的控件开发(之前 ...

  5. Android 自定义控件开发入门(一)

    那么怎样来创建一个新的控件呢? 这得看需求是怎样的了. 1.需要在原生控件的基本功能上进行扩展,这个时候你只需要继承并对控件进行扩展.通过重写它的事件,onDraw ,但是始终都保持都父类方法的调用. ...

  6. PyQT5学习之旅 1 如何自定义控件,入门做一个上位电脑串口调试软件,全部开源。(附带源码)

    文章目录 一.前言 二.开发的必备工具 2.1 PyCharm 如何集成 QT Designer UI代码转可视化 可视转化UI代码 打包成 exe 软件: 2.2.引进自定义控件 移除此控件为自定义 ...

  7. Android自定义控件开发入门与实战(11)Xfermode,Android程序员如何有效提升学习效率

    mPaint = new Paint(); mPaint.setColor(Color.BLACK); mBitmap = BitmapFactory.decodeResource(getResour ...

  8. Android自定义控件开发入门与实战(7)SVG动画,android底层架构

    move to (50,23) line to(100,25) 而坐标并不是用width和height的坐标,而是viewportWidth和viewportHeight的坐标,(50,23)中50表 ...

  9. Android-史上最优雅的实现文件上传、下载及进度的监听,android自定义控件开发入门与实战

    注:如果需要对Http的返回值做解析,可在使用uploadProgress操作符时,传入一个解析器Parser 下载 //文件存储路径 String destPath = getExternalCac ...

最新文章

  1. 学习javascript 的一点感想
  2. java同时执行同一个方法吗_java 返回结果的同时执行另一个方法
  3. SuperMap 房产政务协同管理平台
  4. 基于 Kotlin 一行代码实现 android 导航栏 BottomBar
  5. MySQL的日志管理
  6. MEP(minimum error pruning) principle with python implemention
  7. Intel VT-x 处于禁用解决方法
  8. POJ 1398 Complete the sequence! ★ (差分)
  9. 某高手毕生精力总结的电脑技巧
  10. 那些远去的人,那段伟大的历史【ZZ】
  11. css3实现进度条的模拟
  12. Android添加垂直滚动ScrollView 常见问题
  13. 硅谷系创业公司,这家深耕物联网22年的Fabless终于走到上市关口
  14. i3cpu驱动xp_Intel英特尔 Core i3/Core i5/Core i7系列CPU显示驱动 14.46.9.5394版 For WinXP-32...
  15. 前端知识总结之基础知识
  16. Echarts--市地图
  17. Office 365 函数之Right函数
  18. Python Web开发——Django框架学习
  19. Git如何删除本地仓库
  20. Pytorch读取Mxnet的rec格式数据

热门文章

  1. Android高手进阶教程(一)-------Android常用名令集锦(图文并茂)!
  2. 打开电脑任务管理器的方法
  3. js实现图片拖拽,定点缩放,旋转 (二)
  4. WEBRTC + vue 建立连接 本地测试
  5. AMD Opteron 185 + ATI RDX200 安装雪豹成功。
  6. win7和ubuntu实现相互复制粘贴文件
  7. 美国限制H1-B签证将导致科技岗位外流
  8. 你不得不熟悉且熟练掌握的前端知识
  9. ubuntu系统中用c语言编写简单程序
  10. 程序员必备画图技能之——流程图