Android自定义控件二

文章介绍了如何在Android中扩展ListView,实现上拉刷新和下拉加载功能。通过自定义头部和尾部布局,结合形状绘制和旋转动画创建进度条效果。在处理触摸事件中,计算移动偏移量以判断刷新状态,并通过接口回调通知数据加载。同时,文章还讨论了上拉加载的实现策略,以及Scroller如何用于平滑滚动效果,并涉及View的触摸事件分发机制。
摘要由CSDN通过智能技术生成

#自定义控件二

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>

下拉刷新

  1. 定义一个类继承ListView.

  2. 当作一个普通的Listview使用, 给绑定数据.

  3. 定义头布局, 在自定义的ListView的构造函数中, 添加头布局.

     addHeaderView(headerView)
    
  4. 默认情况下, 把头布局隐藏.(修改paddingTop为负值)

     // 测量头布局的高度.
     headerView.measure(0, 0);	// 让系统框架去帮我们测量头布局的宽和高.
     // 取出头布局的高度.
     //headerView.getHeight(); // 此方法是控件没有显示到屏幕上之前是获取不到值的, 一直都是0
     headerViewHeight = headerView.getMeasuredHeight(); // 获得测量后的高度
     
     // 隐藏头布局. paddingTop
     headerView.setPadding(0, -headerViewHeight, 0, 0);
    
  5. 处理onTouchEvent事件

    • 按下时: 把y轴的值记录下来

    • 移动时: 记录移动后y轴值,计算一个移动偏移量

         移动后的 - 按下的 = 距离.
        
         -头布局的高度 + 距离 = 最新的paddingTop的值.
      
         设置头布局的paddingTop的值.
        
         状态的判断:
        	if(paddingTop>0完全显示&&当前是下拉刷新状态){
        		将headerView设置为松开刷新状态
        		旋转箭头 0 -180
        		修改文本为松开刷新状态
        		
        	}else (paddingTop<0 未完全显示&&当前是松开刷新状态){
        		将headerView设置为下拉刷新状态
        		旋转箭头 -180 -360
        		修改文本为下拉刷新状态
        	}
      
    • 抬起时:

        if(当前是下拉刷新状态){
        	无效拖动,隐藏头布局
        }else if(当前是松开刷新状态){
        	设置为刷新状态
        	修改padding为0
        	隐藏箭头并清除动画
        	显示进度条
        	修改文本为正在刷新
        }
      

接口回调(重要)

通过事件监听机制,降低模块之间的耦合度。

我暴露给你接口,你给我一个回调。当事件发生的时候,我调用你的回调。

上拉加载

  • 加载更多脚

    1. 定义脚布局, 添加到ListView的尾部.

    2. 隐藏脚布局.

    3. 在onScrollStateChanged方法中, 判断当前是否到底部. 显示脚布局, 调用回调事件.

    4. 提供方法让调用者设置停止刷新。在停止刷新里执行隐藏脚布局等收尾动作.

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.

    • 主界面.
    • 菜单界面.
  • 侧滑菜单, 实现步骤:

    1. 定义类继承ViewGroup.

    2. 在布局文件中定义.

    3. 定义菜单布局和主界面布局, 把它俩整合在SlideMenu一个控件中.

    4. 在onMeasure方法中测量, 测量出主界面和菜单界面的宽和高.

    5. layout布局, 把菜单界面和主界面放在指定的位置上.

    6. 实现onTouchEvent方法. 处理事件, 把菜单一点一点的拉出来.

      • 按下: 记录下x轴的偏移量: mMostRecentX 40(最新的x轴偏移量);

      • 移动: 得到移动后x轴的值: moveX = 42

        1. 计算差值: mMostRecentX - moveX = 48 - 42 = 6;
        2. 限定左右边界.
          • 左边界: (当前x轴的偏移量 + 差值) < -菜单宽度, 如果小于, 一直赋值为: -菜单宽度
        3. 根据差值, 使用scrollBy方法移动屏幕. scrollBy(6, 0);
        4. 需要把mMostRecentX重新赋值, 赋值为moveX. mMostRecentX = 48;
      • 抬起: 获得当前x轴的偏移量, 判断是否大于-菜单的宽度的一半, 如果大于, 切换到主界面.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

paterWang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值