#自定义控件二
ListView上拉刷新与下拉加载
ListView, 是在原有的ListView的基础上, 增加下拉刷新头, 和加载更多的尾.
shape绘制进度条
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="0"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="360">
<!--旋转动画
android:fromDegrees="0"//开始角度
android:pivotX="50%"//以宽度的50%为轴心X坐标
android:pivotY="50%"//以高度的50%为轴心Y坐标
android:toDegrees="360"//结束角度
-->
<!--android:innerRadius//环的内半径 dimen值
android:innerRadiusRatio//环的内半径与ring的宽度之比,会被innerRadius的值覆盖 默认为9
android:thickness//环的厚度 dimen值
android:thicknessRatio//环的厚度与ring的宽度之比。会被android:innerRadius的值覆盖 默认为3
android:useLevel//如果作为LevelListDrawable的一个item使用,则设置true,否则设置false,不然无法显示
-->
<shape
android:innerRadiusRatio="3"
android:shape="ring"
android:thicknessRatio="15"
android:useLevel="false">
<gradient
android:centerColor="#FF6A6A"
android:endColor="#FF0000"
android:startColor="#FFFFFF"
android:type="sweep" />
<!--渐变色
android:type="sweep"//sweep:扫描式渐变 linear:线性渐变 radial:辐射式渐变(配合gradientRadius使用,辐射渐变的半径)
-->
</shape>
</rotate>
下拉刷新
-
定义一个类继承ListView.
-
当作一个普通的Listview使用, 给绑定数据.
-
定义头布局, 在自定义的ListView的构造函数中, 添加头布局.
addHeaderView(headerView)
-
默认情况下, 把头布局隐藏.(修改paddingTop为负值)
// 测量头布局的高度. headerView.measure(0, 0); // 让系统框架去帮我们测量头布局的宽和高. // 取出头布局的高度. //headerView.getHeight(); // 此方法是控件没有显示到屏幕上之前是获取不到值的, 一直都是0 headerViewHeight = headerView.getMeasuredHeight(); // 获得测量后的高度 // 隐藏头布局. paddingTop headerView.setPadding(0, -headerViewHeight, 0, 0);
-
处理onTouchEvent事件
-
按下时: 把y轴的值记录下来
-
移动时: 记录移动后y轴值,计算一个移动偏移量
移动后的 - 按下的 = 距离. -头布局的高度 + 距离 = 最新的paddingTop的值. 设置头布局的paddingTop的值. 状态的判断: if(paddingTop>0完全显示&&当前是下拉刷新状态){ 将headerView设置为松开刷新状态 旋转箭头 0 -180 修改文本为松开刷新状态 }else (paddingTop<0 未完全显示&&当前是松开刷新状态){ 将headerView设置为下拉刷新状态 旋转箭头 -180 -360 修改文本为下拉刷新状态 }
-
抬起时:
if(当前是下拉刷新状态){ 无效拖动,隐藏头布局 }else if(当前是松开刷新状态){ 设置为刷新状态 修改padding为0 隐藏箭头并清除动画 显示进度条 修改文本为正在刷新 }
-
接口回调(重要)
通过事件监听机制,降低模块之间的耦合度。
我暴露给你接口,你给我一个回调。当事件发生的时候,我调用你的回调。
上拉加载
-
加载更多脚
-
定义脚布局, 添加到ListView的尾部.
-
隐藏脚布局.
-
在onScrollStateChanged方法中, 判断当前是否到底部. 显示脚布局, 调用回调事件.
-
提供方法让调用者设置停止刷新。在停止刷新里执行隐藏脚布局等收尾动作.
-
ListView的滚动监听
ListView.setonScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
//滚动状态发生变化。0停止,1手指拖动,2惯性滑动
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,int visibleItemCount, int totalItemCount) {
//当listview正在滚动的时候调用的方法
//firstVisibleItem:第一个显示的Item的位置
//visibleItemCount:可显示的Item的总数
//totalItemCount:Item的总数
});
上拉加载实现方案的缺陷补救
private boolean flag = false;
private OnScrollListener onScrollListener;
setOnScrollListener(this);//在构造函数里调用过以后,修改标记,代表自己已经用用过
flag = true;
@Override
public void setOnScrollListener(OnScrollListener l) {
if (!flag) {//如果是自己用,走原本逻辑
super.setOnScrollListener(l);
} else {//如果不是自己用,抛出异常
// throw new UnsupportedOperationException(" Please use xxx ,dont't XJBD!");
onScrollListener=l;
}
}
//事件传递出去
if (onScrollListener != null) {
onScrollListener.onScrollStateChanged(view, scrollState);
}
if (onScrollListener != null) {
onScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
}
onMeasure
onMeasure(int widthMeasureSpec, int heightMeasureSpec);
widthMeasureSpec: 父控件强加的水平方向的尺寸约束
heightMeasureSpec:父控件强加的垂直方向的尺寸约束
32bit的int值,二进制的前两位代表测量模式,后面其余30位代表specSize
比如:widthMeasureSpec = 1073742304,
对应的二进制:
0100 0000 0000 0000 0000 0001 1110 0000
01代表MeasureSpec.EXACTLY
111100000代表specSize=480
MeasureSpec.MODE:
- EXACTLY 表示测量者已经给该View确定了大小–specSize.但被测量者可以(不一定非要)自行决定
- AT_MOST 表示测量者约束该View最多只能是specSize的大小。被测量者可以自行决定,只要不超过
- UNSPECIFIED 表示由被测量者View自己去定义大小。
onLayout
/*
changed: true,View布局或者位置发生了变化
l:相对父控件,左边界X轴的距离
t:相对父控件,顶边界Y轴的距离
r:相对父控件,右边界X轴的距离
b:相对父控件,底边界Y轴的距离
*/
protected void onLayout(boolean changed, int l, int t, int r, int b)
scrollTo与scrollBy
让显示区域滚动。UI上的展现则为,往右滚动,显示内容(布局)往左滚动。
- scrollTo:显示区域滚动到某个位置
- scrollBy:显示区域基于原来位置递增某个距离。
SlideMenu的触摸事件
case MotionEvent.ACTION_DOWN:
downX = (int) event.getX();//记录按下X
break;
case MotionEvent.ACTION_MOVE:
int moveX = (int) event.getX();
int deltaX = downX - moveX; //计算偏移量
// 判断给定当前的增量移动后, 是否能够超出边界.
scrollX = getScrollX() + deltaX;
if(scrollX < -getChildAt(0).getMeasuredWidth()) {
// 当前超出了左边界, 应该设置为在菜单的左边界位置上.
scrollTo(-getChildAt(0).getMeasuredWidth(), 0);
} else if(scrollX > 0) {
// 当前超出了右边界, 应该设置为0
scrollTo(0, 0);
} else {
scrollBy(deltaX, 0);
}
downX = moveX;//重新赋值以便下次事件处理
break;
case MotionEvent.ACTION_UP:
// 获取中轴线:菜单宽度的一半位置
int center = -getChildAt(0).getMeasuredWidth() / 2;
scrollX = getScrollX(); // 当前屏幕左上角的值
if(scrollX > center) {
currentScreen = SCREEN_MAIN;
} else {
currentScreen = SCREEN_MENU;
}
switchScreen();//切换屏幕
break;
Scroller–用时间控制滚动过程
//startX:开始坐标
//startY:开始Y坐标
//dx:X坐标增量
//dy:Y坐标增量
//duration:执行的时间
scroller.startScroll(startX, 0, dx, 0, duration);//这句话并不会执行滚动操作,也不会模拟数据,只是配置信息。
invalidate(); -> drawChild -> view.draw() -> view.computeScroll() ->invalidate();
scroller.computeScrollOffset();//模拟数据,返回false,说明模拟完毕。
@Override
public void computeScroll() {
if(scroller.computeScrollOffset()) {// 模拟数据,同时也是递归退出逻辑
// 把当前scroller正在模拟的数据取出来, 使用scrollTo方法切换屏幕
int currX = scroller.getCurrX();
scrollTo(currX, 0);
invalidate(); //递归逻辑,造成computeScrollOffset的循环调用,不断模拟数据
}
}
PS:duration的时间是由递归逻辑硬生生消耗完的。
View的触摸事件分发机制
dispatchTouchEvent//分发事件
onInterceptTouchEvent//拦截事件
onTouchEvent//处理事件
侧滑菜单练习步骤
-
侧滑菜单组成. 继承 ViewGroup(内部可以包含多个子控件), View 和 ViewGroup的关系, ViewGroup是继承自View.
- 主界面.
- 菜单界面.
-
侧滑菜单, 实现步骤:
-
定义类继承ViewGroup.
-
在布局文件中定义.
-
定义菜单布局和主界面布局, 把它俩整合在SlideMenu一个控件中.
-
在onMeasure方法中测量, 测量出主界面和菜单界面的宽和高.
-
layout布局, 把菜单界面和主界面放在指定的位置上.
-
实现onTouchEvent方法. 处理事件, 把菜单一点一点的拉出来.
-
按下: 记录下x轴的偏移量: mMostRecentX 40(最新的x轴偏移量);
-
移动: 得到移动后x轴的值: moveX = 42
- 计算差值: mMostRecentX - moveX = 48 - 42 = 6;
- 限定左右边界.
- 左边界: (当前x轴的偏移量 + 差值) < -菜单宽度, 如果小于, 一直赋值为: -菜单宽度
- 根据差值, 使用scrollBy方法移动屏幕. scrollBy(6, 0);
- 需要把mMostRecentX重新赋值, 赋值为moveX. mMostRecentX = 48;
-
抬起: 获得当前x轴的偏移量, 判断是否大于-菜单的宽度的一半, 如果大于, 切换到主界面.
-
-