自定义控件基础02_下拉刷新_侧拉菜单_自定义属性

自定义控件02

自定义控件

①,纯粹自定义绘制

②,在原生的基础上追加功能.

1,下拉刷新功能(继承ListView追加功能)(下拉刷新,加载更多,两个功能)

1.1 下拉刷新

①创建一个类,继承ListView

创建自定义适配器,设置数据

额外:自定义控件会放到view包下

②自定义控件的头(即下拉的时候显示的view)

推荐名称initHeaderView();在构造方法中初始化这个头

this.addHeaderView()//添加一个头布局的控件,在listView顶部添加一个头

头部ui参考,左边的小箭头在刷新的时候是一个圆环滚动(Progress Bar)可以考虑用帧布局实现(松开的时候显示为ProgressBar)

③创建一个自定义颜色的ProgressBar

创建xml文件,先写rotate节点(旋转动画)观察可知,转圈是从0-360度旋转

参照物以自身为中心.

在这个xml文件中再写一个rotate的同级节点shape,

shapes属性:ring环形

(不过安卓下是全是方形,实际上是一个正方形背景透明,指定了一个环形)

属性innerRadiusRatio=”内半径比”

环形的半径指定的正方形宽/半径比

属性thicknessRatio=”厚度比”

环形的厚度>>指定的宽度/厚度比(可以看做外层一圈环形)

useLevel=”false”//不停旋转

子节点gradient 渐变色(推荐灰色为主,指定开始,中间,结束颜色)

安卓下实际上环形头为结束颜色,尾巴的颜色为开始颜色

属性type=”sweep”// 三种颜色扫过

最后把shape整个节点放到rotate节点下,之前放在同级,是为了避免编写时候报错

④隐藏头布局(通过设置paddingTop为负数即可,这个负数为头布局的高度)

头布局的高度一定要设置为wrapcontent包裹内容

headerView.getHXX()//获取高度的时候,如果控件没有显示到界面是获取不到的.

//获得一个测量后的高度,只有在测量之后才能获取到.

headerView.getMeasuredHeight();

注意,这个View的布局根节点必须是LinearLayout

 

//手动触发测量头布局的高度,

headerView.measure(0,0)//让系统框架去测量头布局的宽高

⑤滑出头布局

获取手指拖动的间距,设置headerView的paddingTop

=-headerViewHeight+间距

间距 = 移动的Y轴 - 按下的Y轴

额外 当paddingTop大于-headerViewHeight的时候(即间距大于0的时候),才设置paddingTop的数值

判断第一个显示的条目是否是LIstView的第一个条目.

getFirstVisiablePostion();//获取第一个可见条目的索引

如果不是,就返回super.onTouchEvent(ev)//LisrVIew默认的效果

如果是,就返回true,自己处理事件

1.2下拉刷新>>滑动中头布局状态(圆环的状态)

状态影响的通用控件:

TextView状态文本根据状态修改文本

箭头的指向(默认下拉刷新,为向下的状态)

圆环的显示

刷新状态的动画状态抽取一个方法来判断

①如果paddingTop小于0

头布局没有完全显示,显示为向下的箭头,并且当前状态为松开刷新之后,才重新进入(即反方向拖动,取消刷新)下拉刷新状态

重新进入下拉刷新状态,箭头动画效果(逆时针从-180度>>-360度,以自身为中心),

//记得当控件停止在动画播放完毕的状态

am.setFillAfter(true);

②如果paddingTop大于0

头布局完全显示,显示为向上箭头,并且当前状态为下拉刷新状态,就进入松开刷新状态.

进入松开刷新状态:改变箭头动画效果(逆时针走180度-180()向上,以自身为中心)

//记得当控件停止在动画播放完毕的状态

am.setFillAfter(true);

③松开手指的时候,

当状态为松开刷新状态,状态就为刷新中状态

箭头图片设置消失.clearAnimation()//同时清除掉自身的动画

圆环设置可见

然后再把头布显示出来:paddintTop = headerView.height

当状态为下拉刷新状态时,什么都不做,设置隐藏,paddingTop=-headerView.height.

1.3  下拉刷新(回调事件)

进入刷新中状态调用接口中的方法,这样调用者就能在这个方法里写刷新中的逻辑

创建一个方法(参数为接口对象)提供给调用者使用.

额外:进入刷新状态的时候,就不让用户继续拖动了(判断状态为刷新中,就直接跳出触摸事件)

调用这个方法,创建一个子线程,一段时间后,添加一条数据给ListView(集合中添加一条数据,更新适配器即可)

再提供一个方法给调用者,调用此方法通知ListView已经刷新完了

刷新完了,就把头部隐藏,状态更改为下拉刷新,设置圆环状态,箭头状态等.

还有最后的刷新时间.

时间格式:SimpleDateFormat = new XXXX(pattern);//pattern正则表达式

参考正则表达式:yyyy-MM-dd HH:mm:ss

Sdf.format(时间)

额外:默认最开始的时候也要设置一次更新时间

1.4 加载更多功能的实现

1.4.1 功能分析 只要用户拖到了底部,就触发加载更多的功能.

①setOnScrollListener(this)//设置滚动监听事件

重写的方法中,

//当滚动状态发生变化的时候调用

onScrollStateChanged(AbsListView view, int scrollstate)

Scrollstate>>

OnScrollStateListener.Scroll_State_IDIE;//停滞状态

Scroll_state_touch_scroll; //手指触摸在屏幕上滑动

Scroll_State_Fling;//手指快速滑动一下

②判断事件

//当前状态是停滞状态,并且屏幕上显示的最后一个条目的索引是ListVIew条目-1

就代表滑动到了底部

额外:注意监听事件的注册位置

当前状态是手指快速滑动也需要监听,因为是有惯性效果的,它不触发停滞状态.

1.4.2 加载更多的布局(加载更多只需要显示或隐藏,不用考虑拖动显示事件)

①添加脚布局this.addfooterView(view)

参考ui

②脚布局状态设置

默认状态应该为隐藏的,设置paddingTop为自己高度的负数

要注意,不能直接获取到高度,要先measure(0,0)测量一下,再获取测量的高度

当滑动到底部的时候,设置脚布局的padingTop为0即可

细节:滑动的时候不能直接滑动到底部,

setSelection(getCount())//滑动到最底部(多显示一条)

可以多次滑动到底部,触发加载更多事件,不合理,同一时间应该只能加载一次.

设置一个变量去控制它

③刷新监听器增加一个回调事件,加载更多的脚布局出现时,调用该方法.

④ListView继承类中用户调用刷新完毕的方法中添加隐藏脚布局的逻辑

设置paddingTop为-footView的高.

最后把控制变量置为默认

2.侧拉菜单(SlideMenu)功能

参考最终ui

2.1 菜单和主界面布局的实现

这是一个带有组合布局自定义控件.不适合直接继承view.

需要继承VIewGroup(View组)适合实现组合布局.

继承View的自定义控件,不需要重写onLayout()方法,因为它不包含布局

如果是继承ViewGroup的自定义控件,是必须要重写onLayout()方法.

因为它必须要有布局.有子孩子(例如LinearLayout也是继承ViewGroup的)

2.1.1 组合布局,主界面和菜单是分开的两个View

①菜单的View,是可以滚动的,所以根节点可以用ScrollView(当然也可以LinearLayout下一个ScrollView包含子节点,但是没必要,直接用它做根节点即可)

条目(可以用TextView,没必要单独写一个条目布局)带有状态选择器(pressed状态)

菜单参考ui(高度包裹父窗体,宽度固定值):

写一个颜色的xml文件(colors.xml)保存颜色.

因为每一个条目的的样式基本类似,所以可以抽取出style样式

最后在组合布局控件中引用子孩子

<include layout=”@Layout/xxxx”/>

②主界面参考ui:

ImageButton 有默认的背景颜色,可以手动指定透明颜色

ImageButton旁边有一条细线,这是一个图片,为了好看一点,让它上下有点距离,

它的右边还有一个TextView,下面的空白区域随便写点什么

最后在组合布局控件中引入子孩子

<include layout=”@Layout/xxxx”/>

ViewGroup中子孩子的顺序从上至下,由0开始.

2.2 测量和布局

2.2.1,SlideMenu组合控件继承自ViewGroup,控件的组合,是由菜单和主界面组成的.

①在onMeasure方法中测量菜单和主界面的宽高

②在onLayout方法中给菜单和主界面两个View进行布局(放置位置)

2.2.2,测量onMeasure(widthMeasureSpec,hxxx).

由于这个组合控件的宽高在布局中是填充父窗体

所以参数widthMeasureSpec(测量宽)和hxxx都是代表着填充屏幕

①测量菜单的宽和高

View menuView = getChildAt(0)//获得索引为0的子孩子(菜单)

menuView.measure(

menuView.getLayoutParams().width(通过这个View对象的布局参数获取宽度信息)),

hxxxx(如果子控件设置的包括父窗体就直接使用方法的参数)

②测量主界面的宽高

View mainView = getChildAt(1)//获取所以为1的子孩子(主界面)

Main.measure(wxxxx,hxxxx);

2.2.3,布局onLayout(boolean changed,int left,int top,int right,int botton)方法

这四个参数代表SlideMenu这个组合控件的左(0)上(0)右(宽)下(高)

①主界面的位置放到屏幕的左上角,平铺下来(宽高都设置到父控件最大)

获取mainView

mainView.layout(l,t,r,b)//设置布局位置

②菜单的位置(最初默认是在屏幕外X轴负坐标轴隐藏起来)

获取menuView控件

menuView.layout(菜单宽度取负数,0,0(与Y轴重合),b)

额外:requestWindowFeature(Window.Feature_No_Title)//代码里去掉标题

2.3 ScrollTo()和scrollBy()事件处理逻辑原理

2.3.1方法介绍

①scrollTo(int x,int y)

给定固定的偏移量,屏幕会显示到对应的位置上

(从最开始的起点为基点,而不是上一次移动的点,最开始起点一般为屏幕左上角0.0)

②scrollBy(int x,int y)

给定移动的值,会把屏幕原来左上角的X轴左边值取出来加上给定的值(即在每一次移动后的基础上移动)计算新的值,移动到对应的位置.

2.4,touch触摸事件的处理

按下:记录下X轴的值,downX;

移动:记录下移动X轴的值,moveX

计算增量值 = downX-moveX(因为屏幕移动和控件移动的显示是相反)

使用ScrollBy(增量值,y)//移动到对应的位置

抬起:

①获取按下的值和移动的值,得到增量值(down-move,理论上绝对值固定为1?(已解决,并不是固定为1,moveX的值是根据单位时间内获取一次,而不是一个像素点一次))

Scrollby(增量值,0);//滚动到相应的位置

移动的值重新赋值给按下的值

额外1:边界会不合理的超出(不符合用户的预期)

解决1:判断移动的值是否会超出边界

getScrollby()+增量值//获取当前已经移动的值+增量值,是否超出边界

左边界不能超出菜单栏的左边界(<=菜单栏的宽度取负数)

右边界不能超出主界面的右边界(<=主界面的宽度)

优化考虑:如果超出了,直接return掉,是否效率更高?,不用再调用方法滚动.

 

②松开的时候,判断菜单是否需要显示在可视界面上.

这里以菜单栏宽度的一半(取负数)为标准,与移动的值做比较

如果移动的值大于菜单栏宽度的一半,就切换到主界面

如果移动的值小于菜单栏宽度的一半,就切换到菜单栏

抽取一个方法,根据不同的情况,设置移动的值ScrollTo(xx,0);

额外1:松开的时候,没有一个滚动的效果,而是直接跳过去了

解决1:可以自己模拟数据,来实现滚动的效果,但是太麻烦(计算值,每秒移动值等)

android中提供了一个Scroller类来实现该数据模拟

代码实现步骤:

scroller.startScroller(sx,sy,dx,dy,duration)//模拟滚动的效果

sx:开始的位置,dx,结束的位置

duration:持续时间

①分析各个参数具体的值

开始的位置:最后一次移动后屏幕的点,sx = getScrollX();

结束的位置:增量值,目的地的值-开始位置的值

持续时间应该是动态的,不然移动位置太短,时间就会显示很长,干脆动态的设置为增量值*10毫秒

②scroller.startScroller(x,x,x,x)//该方法只是去模拟值,但是不负责显示设置的值

所以还需要自己去移动切换屏幕

用一个while循环,当数据在模拟的时候,不停的取值切换屏幕

不过,谷歌已经提供了方法来实现这个功能

Invalidate();//刷新当前控件,不断调用onDraw()方法

但是ViewGroup父类中是没有onDraw()方法的.不过它有drawChild(xxx)方法,绘制子控件,所以继承它的组合控件也会去调用drawChild(XXX)方法

③查看源码可知:

drawChild()>>return child.draw(xxxx)//调用每一个子控件的draw方法

调用的是view.draw(xxxx)方法(三个参数的,直接跳过去看到的是一个参数的)

>>这个方法里可以看到调用了一个view.computeScroll()方法,注释翻译:调用在父类去请求更新(可以覆盖掉)scrollX,scrollY的值,然后进行移动的操作

那么在这里就可以去模拟一些数据去更新这两个x,y的值

在computeScroll()方法的注释上也可以看到谷歌是建议使用Scroller模拟数据

④重写view.computeScroll()方法,取出模拟的数值

int currX = scroller.getCurrX()//取出正在模拟的数值

scrollTo(currX,0);//把对应的数值传递过去

调用一次invalidate()方法,只触发一次computeScroll()//方法

所以在computeScroll()方法中再调用invalidate()方法

类似与递归,需要找到一个出口,让这个递归停下来

如果数据模拟完毕,就不再进行递归调用了

scroller.computeScrollOffset()//返回为true 代表这个动画(数据模拟)还未完成.

2.5 点击切换屏幕

①点击ImageButton 切换屏幕显示

实际上就是切换这个自定义组合控件在屏幕上显示的位置

判断当前显示的状态

如果显示的是菜单界面,就切换到主界面完全显示

如果显示的是主界面,就切换到菜单界面的显示.

切换显示的效果可以用上面实现的界面移动逻辑(优化时间显示)

②菜单栏里每一个小条目的点击事件,点击完之后都会隐藏菜单栏,完全显示主界面.这里可以把点击事件写在样式里,这样每一个条目都有对应的效果了.

2.6 事件分发机制

问题描述:设置完点击事件了,在菜单栏中无法拖动,一旦拖动,走的都是点击事件,而不是预期中的切换屏幕显示,是由于事件分发机制引起的问题.

2.6.1 事件分发机制的原理

①方法

每一个ViewGroup都有下面的方法

dispatchTouchEvent()//分发事件用的方法

onInterceptTouchEvent();//拦截事件用的方法

onTouchEvent();//处理事件用的方法

每一个View都有下面的方法

dispatchTouchEvent()//分发事件用的方法

onTouchEvent();//处理事件用的方法

②当一个事件开始了,会走最顶层的ViewGroup(父控件)的事件分发>>

>>拦截事件 判断拦截事件的返回值

返回true  拦截这个事件,就传递给自己的onTouchEvent()处理事件,事件终止

返回false 就不需要处理,传递给下一层,判断是否拦截

事件一直到传递到最下面的子控件view,它是不包含子控件的,也就没有拦截事件的方法去判断是否拦截,直接走view的onTouchEvnet()

返回为true,处理事件,事件终止

返回为flase,不处理时事件,向上回传

向上回传,上一级ViewGroup的onTouchEvent是否处理,同样的继续回传或处理.

事件一直到最上层的父控件onTouchEvent方法中

如果返回为true处理当前事件

如果返回为false不处理当前事件,事件直接消失掉,

参考流程图如下

2.6.2代码实现

在自定义组合控件代码中拦截事件onInerceptTouchEvent(event)

//判断是否是横着滑动

就是按下与松开的位置X轴之差(绝对值)大于某一个值就代表是横着滑动的,拦截掉这个事件,比如大于10的时候就返回一个true,拦截掉这个事件

这个值为10在某些屏幕手机上使用可能不太合理.

所以使用google提供的VIewConfiguration.get(getContext()).getTouchSlop()它返回的值是根据不同手机屏幕返回的,用它来做滑动事件判断标准比较合理

注意:事件分发在面试的时候问的比较多,要多理解掌握

 

3其它补充

自定义属性:使用

在一个布局文件根节点中 xmlns属性(xml属性的命名空间)

可以指定多个xmlns指定不同的命名空间

xmlns:xxxx(自定义名称)="http://schemas.android.com/apk/自定义名称(res-auto参考)

创建 res下values创建根节点为resources的xml文件

子节点declare-styleable name=”一般为使用它的文件名”

这个节点下的子节点

attr节点 format属性,可以指定自己想要的属性,指定frxxx就可以使用资源文件

在自定义控件的布局构造中(两个参数的,attrs方法)

attrs.getXXX可以获得布局文件中的参数

时间: 2024-12-14 09:05:31

自定义控件基础02_下拉刷新_侧拉菜单_自定义属性的相关文章

Android—自定义控件实现ListView下拉刷新

这篇博客为大家介绍一个android常见的功能——ListView下拉刷新(参考自他人博客,网址忘记了,阅读他的代码自己理解注释的,希望能帮助到大家): 首先下拉未松手时候手机显示这样的界面: 下面的代码是自定的扎样的控件: package com.dhsr.smartID.view; import android.content.Context; import android.util.AttributeSet; import android.view.Gravity; import andr

Android自定义控件——ListView的下拉刷新与上拉加载

转载请注明出处:http://blog.csdn.net/allen315410/article/details/39965327 1.简介 无疑,在Android开发中,ListView是使用非常频繁的控件之一,ListView提供一个列表的容易,允许我们以列表的形式将数据展示到界面上,但是Google给我们提供的原生ListView的控件,虽然在功能上很强大,但是在用户体验和动态效果上,还是比较差劲的.为了改善用户体验,市面上纷纷出现了各种各样的自定义的ListView,他们功能强大,界面美

Android SwipeRefreshLayout:谷歌官方SDK包中的下拉刷新

 <Android SwipeRefreshLayout:谷歌官方SDK包中的下拉刷新> 下拉刷新在如今移动开发中应用如此广泛和普遍,以至于谷歌干脆在SDK中给予支持.在android-support-v4包中,谷歌增加了SwipeRefreshLayout,该组件提供基础的下拉刷新表现能力和开放出来供开发者调用的基本接口.现在给出一个简单的代码例子加以说明. 代码工程简要说明:以一个SwipeRefreshLayout包裹ListView,SwipeRefreshLayout接管List

Android零基础入门第72节:SwipeRefreshLayout下拉刷新

在实际开发中,经常都会遇到下拉刷新.上拉加载更多的情形,这一期就一起来学习Android系统的SwipeRefreshLayout下拉刷新组件. 一.SwipeRefreshLayout简介 SwipeRefrshLayout是Google官方更新的一个控件,可以实现下拉刷新的效果,该控件集成自ViewGroup在support-v4兼容包下. SwipeRefrshLayout常用的几个方法如下: isRefreshing():判断当前的状态是否是刷新状态. setColorSchemeRes

Android基础控件——SwipeRefreshLayout最简单的下拉刷新

还在使用传统的下拉刷新,觉得不够漂亮,怕被产品经理骂吗? 还在忧愁自己技术不够好,不会改造带动画的下拉刷新吗? 那么不要担心,使用SwipeRefreshLayout最简单的下拉刷新,既不失美观又简洁 SwipeRefreshLayout下拉刷新是Google自家的下拉刷新控件,使用过程跟开源库PullToRefresh差不多,废话不多说,开车啦 SwipeRefreshLayout实质上是一个ViewGroup,所以我们将其作为我们的根布局进行演示 经过这个步骤之后,其实在页面上就已经能够下拉

Uni-app基础实战上加载新下拉刷新 WordPress rest api实例(一)

Uni-app实战上加载新下拉刷新 WordPress rest api实例 通过WordPress自带的 rest api接口我们去实现uni-app的上拉刷新和下拉加载,首先我们需要一点基础.如果有基础可以直接看正文,如果大家和枫瑞一样也是新手那大家可以阅读以下文章 uni-app 实战接入热门小说API接口 适用于新手 Uni-App 微信项目练习首页列表含界面传参 新手教程(一) Uni-App 微信项目练习列表传参聊天窗口 新手教程(二) 如果有基础的我们就看这这边哈哈! [tip]1

自定义控件学习——下拉刷新ListView

效果 开始用Android Studio写了,还有挺多不明白这IDE用法的地方....蛋疼 主要思路 1. 添加了自定义的头布局    2. 默认让头布局隐藏setPadding.设置 -自身的高度    3. ListView下拉的时候, 修改paddingTop, 让头布局显示出来    4. 触摸动态修改头布局, 根据paddingTop.          - paddingTop = 0 完全显示        - paddingTop < 不完全显示 -64(自身高度)完全隐藏  

1、ListView自定义控件下拉刷新(一)

1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 android:layout_width="match_parent" 3 android:layout_height="match_parent" 4 android:background="#f5f5f5" > 5 6 <zrc.widget.Zrc

Android之自定义控件-下拉刷新

实现效果: 图片素材:         --> 首先, 写先下拉刷新时的刷新布局 pull_to_refresh.xml: 1 <resources> 2 <string name="app_name">PullToRefreshTest</string> 3 <string name="pull_to_refresh">下拉可以刷新</string> 4 <string name="