`

android自定义布局中的平滑移动

    博客分类:
  • View
阅读更多

        在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()已达到界面刷新的效果,产生手势离开屏幕之后的惯性滑动效果。

 

        可能上面说的比较凌乱,在这里总结一下,大概的思路如下:
        首先我们通过VelocityTrackerViewConfiguration类得到一些惯性滑动所必须的变量,比如手势离开屏幕时的初始速度,允许进行手势操作的最小距离以及允许手势操作的速度边界值;
        第二,创建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 自定义圆环动画切换、流式布局的实现.rar

    在Android中,我们可以使用FlowLayout或者自定义布局来实现这种效果。自定义布局通常会重写onMeasure()和onLayout()方法,以根据子视图的数量和大小动态调整它们的排列。流式布局特别适用于展示多个不固定大小的元素...

    android自定义View滑块移动

    本教程以"android自定义View滑块移动"为例,深入探讨如何实现一个可以在屏幕上自由移动的滑块,并理解Android中View的绘制流程。 首先,我们要明白在Android中创建自定义View的基本步骤。这通常包括以下几个部分: ...

    Android自定义View-圆环布局

    "Android自定义View-圆环布局"是一个专为实现特定视觉效果和交互设计的项目,它包含了圆圈旋转、圆环拖动以及图标拖动和图片缩放等多种功能。 首先,我们来深入理解圆环布局。圆环布局是一种非标准的布局方式,它将...

    android实现自定义RelativeLayout可拖动、缩放、旋转TextView

    - 在ACTION_DOWN事件中记录初始触点坐标,ACTION_MOVE事件中计算移动距离,并更新TextView的位置。 - 使用`LayoutParams`来调整TextView的坐标,调用`requestLayout()`使改变生效。 2. **缩放功能**: - 缩放...

    Android自定义控件之拖动条

    本文将深入探讨如何创建一个美观的自定义拖动条控件,即"Android自定义控件之拖动条"。我们将讨论以下几个关键知识点: 1. **基础知识**:在Android中,基本的滑动条控件是`SeekBar`,它允许用户通过拖动滑块来选择...

    Android自定义布局实现仿qq侧滑部分代码

    在Android开发中,自定义布局是一项重要的技能,它允许开发者根据特定需求定制界面。本篇文章将探讨如何通过自定义布局实现一个仿QQ的侧滑效果。这个效果常见于许多应用中,通常用于显示侧边栏菜单,用户可以通过...

    Android自定义圆形SeekBar

    当用户试图将滑块滑至两端时,阻止它直接到达,而是让其在0%-100%之间平滑移动。 6. **属性定制**: 为了让自定义的CircleSeekBar更具可配置性,我们可以添加一些自定义属性,如圆的半径、滑块的颜色、轨道的颜色等...

    android 继承viewgroup, 自定义布局,拖动效果,增删效果

    在Android开发中,自定义布局是一项重要的技能,它允许开发者根据特定需求创建独特的用户界面。本教程将聚焦于如何继承`ViewGroup`来构建自定义布局,并实现拖动效果以及增删元素时的动画操作。 首先,让我们了解`...

    Android自定义IOS开关

    4. **动画效果**:为了模仿iOS开关滑动的平滑过渡,可以使用`ObjectAnimator`或者自定义动画来实现滑块移动的过程,使切换过程更具有视觉吸引力。 5. **属性设置**:提供一些自定义属性,如滑块颜色、轨道颜色、...

    Android 自定义ViewGroup实现整个Item布局竖直跑马灯效果

    在Android开发中,自定义ViewGroup是实现复杂布局和动画效果的重要手段。本文将深入探讨如何利用自定义ViewGroup来实现一个独特的“竖直跑马灯”效果,这种效果常见于各种信息展示或广告轮播场景,使得内容能沿着...

    Android自定义控件下拉刷新实例代码

    总的来说,Android自定义控件的下拉刷新涉及到以下几个关键步骤: 1. 创建自定义布局,包含刷新指示器(如箭头和进度条)。 2. 在自定义视图中处理触摸事件,判断下拉手势并更新视图状态。 3. 触发刷新逻辑,加载新...

    android 自定义控件 GridView

    在Android开发中,自定义控件是提升应用用户体验和界面独特性的重要手段。GridView作为一种常见的布局控件,通常用于展示网格状的数据,如图片、列表项等。在本主题中,我们将深入探讨如何创建两个可相互拖拉的...

    android自定义歌词显示

    在Android开发中,自定义...总的来说,实现Android自定义歌词显示涉及文件解析、视图绘制、时间同步等多个技术点。通过深入理解这些知识点,我们可以构建出功能完备、用户体验良好的歌词显示功能,提升音乐应用的品质。

    Android自定义组件一[文].pdf

    2. **Title背景移位效果组件**:用于创建标题栏,实现标题背景的平滑移动,包含titleLayout(标题栏布局)、bgImage(背景图片)、bgLeftMargin(背景初始左边距)和animTime(移动动画时间)属性。 3. **ViewPager...

    Android自定义View—仿雷达扫描效果

    最后,作者可能分享了如何在布局文件中添加自定义View,并在运行时根据需要调整参数,如改变扫描速度、范围等。这涉及到XML属性的声明和Java代码中的解析。 总的来说,自定义View—仿雷达扫描效果是一个结合了图形...

    android自定义switch

    本教程将围绕“Android自定义`Switch`”这一主题,深入讲解如何在Android 4.0及以上版本中创建并优化自定义`Switch`。 首先,我们来看`Switch`的基本结构。一个自定义`Switch`通常由两部分组成:滑动条(Track)和...

    Android自定义控件【仿IOS开关】

    首先,我们需要了解Android自定义控件的基本流程。自定义控件通常涉及以下步骤: 1. 创建一个新的Java类,继承自Android的View或ViewGroup类。在这个例子中,我们可以选择继承自CompoundButton,因为Toggle Switch...

    android自定义控件滑动开关源码

    在Android开发中,自定义控件是提升应用用户体验和界面独特性的关键。滑动开关(Slide Switch)是一种常见的UI元素,常用于开启或关闭某个功能。这篇博客文章的配套源码提供了一个自定义滑动开关的实现,让我们深入...

    Android-Android自定义控件之局部图片放大镜--BiggerView

    "Android-Android自定义控件之局部图片放大镜--BiggerView"这个项目,专注于为用户提供一种可以局部放大图片的功能,类似于我们在电商平台浏览商品详情时常见的图片放大镜效果。这种功能能够帮助用户更清晰地查看...

Global site tag (gtag.js) - Google Analytics