在android应用程序的开发过程中,相信我们很多人都想把应用的交互做的比较绚丽,比如让界面切换平滑的滚动,还有热度灰常高的伪3D等界面效果,通常情况下,系统提供的应用在特效这方面只能为我们提供简单的动画接口,所以要想实现比较酷炫的效果还是要自己去开发布局控件(即所谓的自定义View、ViewGroup)。小弟也经常做一些自定义的控件,最近工作比较清闲,所以便将自己对自定义布局控件的一些心得写出来,权当是自己的学习笔记了,各位高手看到了可以忽略。下面就我最近工作中遇到的一个自定义控件开发做一些简单的介绍,其实那个地方原本可以用ScrollView解决很大一部分问题的,但有一些效果确实需要对控件进行重新定义,在继承ScrollView开发中仍然会遇到一些ScrollView自身的限制,所以就仿照ScrollView自己做了一个控件。在其中遇到了一些问题自然就是像ScrollView中拖动的效果(比如快速拖动在手指离开屏幕时控件依旧会由于惯性继续滑动一段距离后才会停止运动),所以就对这个东东做了一下仔细的研究,虽然以前也做过类似的开发,这次由于时间比较充裕,所以将开发中遇到的一些问题都一一记录了下来。下面开始正题:

自定义布局控件自然是要继承某个View或ViewGroup

由于是根据项目的开发来写的这篇博客,所以我就以自定义布局控件(ViewGroup)来做介绍了。

开发一个自定义的ViewGroup自然是要继承ViewGroup类了,在继承这个类之后必须要重写的方法就是

onLayout(boolean changed, int l, int t, int r, int b)

另外至少要有一个构造方法,我个人习惯重写那个有两个参数的构造方法(XXX(Context context, AttributeSet attrs)),因为有了这个构造方法就可以在xml布局文件里使用这个类了。

如果想要对这个布局控件以及其子控件的尺寸进行精确的控制那就要重写下面这个方法了

onMeasure(int widthMeasureSpec, int heightMeasureSpec)

下面开始介绍关于如何让自定义的控件进行平滑的移动,并能够根据手势的情况产生惯性滑动的效果

先介绍一下开发这种滑动效果需要用到的各种工具类:

android.view.VelocityTracker

android.view.Scroller

android.view.ViewConfiguration

VelocityTracker从字面意思理解那就是速度追踪器了,在滑动效果的开发中通常都是要使用该类计算出当前手势的初始速度(不知道我这么理解是否正确,对应的方法是velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity))并通过getXVelocity或getYVelocity方法得到对应的速度值initialVelocity,并将获得的速度值传递给Scroller类的fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY)方法进行控件滚动时各种位置坐标数值的计算,API中对fling方法的解释是基于一个fling手势开始滑动动作,滑动的距离将由所获得的初始速度initialVelocity来决定。关于ViewConfiguration的使用主要使用了该类的下面三个方法:

configuration.getScaledTouchSlop()//获得能够进行手势滑动的距离

configuration.getScaledMinimumFlingVelocity()//获得允许执行一个fling手势动作的最小速度值

configuration.getScaledMaximumFlingVelocity()//获得允许执行一个fling手势动作的最大速度值

需要重写的方法至少要包含下面几个方法:

onTouchEvent(MotionEvent event)//有手势操作必然少不了这个方法了

computeScroll()//必要时由父控件调用请求或通知其一个子节点需要更新它的mScrollX和mScrollY的值。典型的例子就是在一个子节点正在使用Scroller进行滑动动画时将会被执行。所以,从该方法的注释来看,继承这个方法的话一般都会有Scroller对象出现。

在往下就是介绍比较具体的开发思路

首先我们要初始化一些变量,其中的多数代码已经在上面做出介绍了void init(Context context) {

mScroller = new Scroller(getContext());

setFocusable(true);

setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);

setWillNotDraw(false);

final ViewConfiguration configuration = ViewConfiguration.get(context);

mTouchSlop = configuration.getScaledTouchSlop();

mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();

mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();

}

然后我们申明一个用来处理滑动操作的方法fling(int velocityY),代码如下:public void fling(int velocityY) {

if (getChildCount() > 0) {

mScroller.fling(getScrollX(), getScrollY(), 0, velocityY, 0, 0, 0,

maxScrollEdge);

final boolean movingDown = velocityY > 0;

awakenScrollBars(mScroller.getDuration());

invalidate();

}

}

在这个方法里只是使用Scroller的fling方法开始执行fling手势动作了,关于其中的各种参数就不一一解释了。

awakenScrollBars(int startDelay)方法根据我对注释的理解就是在这里给出动画开始的延时,当参数startDelay为0时动画将立刻开始,其实就是一个延迟的作用

下面是对VelocityTracker的初始化以及资源释放的方法private void obtainVelocityTracker(MotionEvent event) {

if (mVelocityTracker == null) {

mVelocityTracker = VelocityTracker.obtain();

}

mVelocityTracker.addMovement(event);

}

private void releaseVelocityTracker() {

if (mVelocityTracker != null) {

mVelocityTracker.recycle();

mVelocityTracker = null;

}

}

onTouchEvent(MotionEvent event)方法的重写public boolean onTouchEvent(MotionEvent event) {

if (event.getAction() == MotionEvent.ACTION_DOWN

&& event.getEdgeFlags() != 0) {

return false;

}

obtainVelocityTracker(event);

final int action = event.getAction();

final float x = event.getX();

final float y = event.getY();

switch (action) {

case MotionEvent.ACTION_DOWN:

LogUtil.log(TAG, "ACTION_DOWN#currentScrollY:" + getScrollY()

+ ", mLastMotionY:" + mLastMotionY,

LogUtil.LOG_E);

if (!mScroller.isFinished()) {

mScroller.abortAnimation();

}

mLastMotionY = y;

break;

case MotionEvent.ACTION_MOVE:

final int deltaY = (int) (mLastMotionY - y);

mLastMotionY = y;

if (deltaY < 0) {

if (getScrollY() > 0) {

scrollBy(0, deltaY);

}

} else if (deltaY > 0) {

mIsInEdge = getScrollY() <= childTotalHeight - height;

if (mIsInEdge) {

scrollBy(0, deltaY);

}

}

break;

case MotionEvent.ACTION_UP:

final VelocityTracker velocityTracker = mVelocityTracker;

velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);

int initialVelocity = (int) velocityTracker.getYVelocity();

if ((Math.abs(initialVelocity) > mMinimumVelocity)

&& getChildCount() > 0) {

fling(-initialVelocity);

}

releaseVelocityTracker();

break;

}

return true;

}

在onTouchEvent方法中,当手势执行到ACTION_UP时获得当时手势的速度值然后判断这个速度值是否大于可滑动的最小速度,如果符合条件那么就执行fling(int velocityY)方法,通过fling方法中的日志发现,在执行了invalidate()方法之后,程序便会执行computeScroll()方法,在computeScroll()方法中执行scrollTo方法主要是因为mScrollX、mScrollY这两个变量的修饰符为portected,无法在扩展类里面无法对这两个变量直接进行操作,那么就需要使用scrollTo方法对这两个变量进行操作,以刷新当前的UI控件,下面附上computeScroll()方法的代码public void computeScroll() {

if (mScroller.computeScrollOffset()) {

int scrollX = getScrollX();

int scrollY = getScrollY();

int oldX = scrollX;

int oldY = scrollY;

int x = mScroller.getCurrX();

int y = mScroller.getCurrY();

scrollX = x;

scrollY = y;

scrollY = scrollY + 10;

scrollTo(scrollX, scrollY);

postInvalidate();

}

}

其中的mScroller.computeScrollOffset()是用来判断动画是否完成,如果没有完成返回true继续执行界面刷新的操作,各种位置信息将被重新计算用以重新绘制最新状态的界面。关于scrollTo方法,我们需要看一下该方法的代码(来自View中):public void scrollTo(int x, int y) {

if (mScrollX != x || mScrollY != y) {

int oldX = mScrollX;

int oldY = mScrollY;

mScrollX = x;

mScrollY = y;

onScrollChanged(mScrollX, mScrollY, oldX, oldY);

if (!awakenScrollBars()) {

invalidate();

}

}

}

我们可以看到,当传递进来的x、y的值与控件当前的mScrollX、mScrollY的值不相同时对界面进行重新计算,根据日志打印的情况来看似乎awakenScrollBars()返回的总是true, 这样的话每执行一次computeScroll()方法,就需要执行一次postInvalidate()方法来刷新界面,而postInvalidate()方法会通过内部线程重新调用invalidate()已达到界面刷新的效果,产生手势离开屏幕之后的惯性滑动效果。

可能上面说的比较凌乱,在这里总结一下,大概的思路如下:

首先我们通过VelocityTracker、ViewConfiguration类得到一些惯性滑动所必须的变量,比如手势离开屏幕时的初始速度,允许进行手势操作的最小距离以及允许手势操作的速度边界值;

第二,创建Scroller的对象,使用它的fling方法供我们控制界面滑动使用;

第三,重写onTouchEvent方法,当我们用手指在屏幕上来回滑动时此时执行的是scrollBy方法来刷新界面,当手指离开屏幕,此时就要开始执行ACTION_UP后面的操作了;

通过对手指离开屏幕时的速度进行判断是否能够进行惯性滑动操作,

如果能够执行那么就使用Scroller类的fling方法启动滑动动画,

这时需要调用一下invalidate()方法来间接的调用computeScroll方法,

在computeScroll方法中对Scroller的动画是否执行完成做了判断,

如果动画没有完成(mScroller.computeScrollOffset() == true)那么就使用scrollTo方法对mScrollX、mScrollY的值进行重新计算刷新界面,

调用postInvalidate()方法重新绘制界面,

postInvalidate()方法会调用invalidate()方法,

invalidate()方法又会调用computeScroll方法,

就这样周而复始的相互调用,直到mScroller.computeScrollOffset()返回false才会停止界面的重绘动作

总结,滑动效果来看,它依然是在不停的计算控件的位置刷新屏幕,不停的绘制新的图片替换旧的图片,当然每次刷新的速度很快,从而给人一种是在快速滑动的感觉,写到这里我发现,现在所谓的动画总是逃脱不了电影的那种模式,每秒播放多少帧的图片来达到连续播放的效果欺骗人的眼睛。

而且,关于android一些酷炫效果的开发,还是要自己多动手,熟悉View、ViewGroup中每个绘制方法、位置计算方法的调用方式以及顺序,那么至少是在2D动画开发中,也就是一种方式,逃脱不了不停重新绘制的这个圈。

关于熟悉View、ViewGroup中每个绘制方法、位置计算方法的调用方式以及顺序的问题,我建议最好自己写一个简单的自定义View或ViewGroup的扩展类,重载那些绘制、位置计算的方法打个日志出来一看自然就明白了,虽然这个方法很笨,但是很容易出效果的

android 自定义布局 根据布局获取类,android自定义布局中的平滑移动之ViewGroup实现...相关推荐

  1. Bootstrap(一)——简介、布局容器和工具类使用(flex布局)

    文章目录 一.Bootstrap简介 二.布局容器 2.1 定宽容器 container 2.2 变宽容器 container-fluid 三.工具类 3.1 颜色与排版 (1)常用颜色 (2)常用排 ...

  2. java 获取类的注解_Java 自定义注解通过反射获取类、方法、属性上的注解

    反射 JAVA中的反射是运行中的程序检查自己和软件运行环境的能力,它可以根据它发现的进行改变.通俗的讲就是反射可以在运行时根据指定的类名获得类的信息. 注解的定义 注解通过 @interface 关键 ...

  3. android页面跳转时获取地址栏,Android 利用scheme页面内跳转协议进行跳转

    什么是 URL Scheme? android中的scheme是一种页面内跳转协议. 通过定义自己的scheme协议,可以非常方便跳转app中的各个页面: 通过scheme协议,服务器可以定制化告诉A ...

  4. Android使用和风天气接口获取天气数据在APP中展示天气

    公司APP项目需要能能够显示当前天气,网上找了很多天气数据接口,总结下来要么收费,要么用起来不友好,最后还是用了郭霖推荐的和风天气接口 这里记录一下自己的使用过程 首先注册和风天气个人开发者,认证时间 ...

  5. android todo,推荐两款Todo类Android应用:高效Todo和Any.do

    8种机械键盘轴体对比 本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选? 我一直在寻找一款这样的Android应用: 首先,他的界面要足够简约,我不喜欢花里胡哨和乱七八糟的 UI: 第二,添加代 ...

  6. FastJson序列化Json自定义返回字段,普通类从spring容器中获取bean

    前言: 数据库的字段比如:price:1 ,返回需要price:1元. 这时两种途径修改: ① 比如sql中修改或者是在实体类转json前遍历修改. ②返回json,序列化时候修改.用到的是fastj ...

  7. android如何查看方法属于哪个类,Android Studio查看类中所有方法和属性

    css-关于位置 当你设置一个你想要相对的模块为relative 你这个模块为absolute 则你的这个absolute会相对relative的那个模块进行移动. 微信公众平台自动回复wechatl ...

  8. 在 GUI 控件中使用布局和容器: CBOX 类

    目录 1. 介绍 2. 目标 3. 类 CBox 3.1. 布局样式 3.2. 计算控件之间的间隔 3.3. 对齐 3.4. 部件渲染 3.5. 部件大小调整 3.6. 递归渲染 4. 在对话框窗口里 ...

  9. python顺序结构有一个入口_高楼万丈平地起,基础要打牢!Python获取类的层次结构和继承顺序...

    上一篇内容我们详细了解了Python使用inspect模块获取一个模块.类.实例.函数的信息及帮助文档的方法(参见新手入门到进阶,你不可不知的模块,用Python获取对象的详细信息). 前情提要 今天 ...

最新文章

  1. [Objective-C] 如何定义Block(块)
  2. MySQL SQL优化
  3. 如何使用Java,Maven,Jetty创建Web应用程序项目
  4. linux服务器情况
  5. 去除报错_转录组分析 | 使用trimgalore去除低质量的reads和adaptor
  6. 我想批量删除专题内最古老的100篇文章
  7. Vue地图导航调用百度地图
  8. [XMAN2018排位赛]ppap
  9. VGG16系列IV: 参数计算
  10. 英语46级报名考试系统
  11. python爬虫爬取微信_Python爬虫爬取微信公众号历史文章全部链接
  12. CISSP 第五章 物理和环境安全
  13. 就是计算机信息学竞赛,什么是信息学竞赛NOI?参加信息学竞赛有什么用?
  14. 基于stm32的两轮自平衡小车1(模块选型篇)
  15. DayThirteen 笔记
  16. 我的期末网页设计HTML作品——咖啡文化网页制作
  17. 学习下 BlackHat Asia 2021 大会议题
  18. 字节、字、位、比特的关系
  19. 白话介绍kubernetes为什么不支持docker了
  20. 设备故障管理系统(一)

热门文章

  1. C++ 学习 之Struct
  2. 6 9*9乘法口诀
  3. 网络协议笔记-数据链路层
  4. 公式化学习urllib(第一卷)
  5. 关于自动装箱和自动拆箱
  6. Android实例-手机安全卫士(三十五)-来电号码显示归属地
  7. db4o官方入门教程翻译--06.集合和数组
  8. @WebFilter()配置servlet访问出现404的原因
  9. BZOJ 3531[Sdoi2014]旅行
  10. 【ACM】 1231 最大连续子序列