A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

前言
前段时间android L(android 5.0)出来了,界面上做了一些改动,主要是添加了若干动画和一些新的控件,相信大家对view的点击效果-水波纹很有印象吧,点击一个view,然后一个水波纹就会从点击处扩散开来,本文就来分析这种效果的实现。首先,先说下L上的实现,这种波纹效果,L上提供了一种动画,叫做Reveal效果,其底层是通过拿到view的canvas然后不断刷新view来完成的,这种效果需要view的支持,而在低版本上没有view的支持,因此,Reveal效果没法直接在低版本运行。但是,我们了解其效果、其原理后,还是可以通过模拟的方式去实现这种效果,平心而论,写出一个具有波纹效果的自定义view不难,或者说很简单,但是,view的子类很多,如果要一一去实现button、edit等控件,这样比较繁琐,于是,我们想是否有更简单的方式呢?其实是有的,我们可以写一个自定义的layout,然后让layout中所有可点击的元素都具有波纹效果,这样做,就大大简化了整个过程。接下来本文就会分析这个layout的实现,在此之前,我们先看下效果。

实现思想
首先我们自定义一个layout,这里我们选取LinearLayout,至于原因,文章下面会进行分析。当用户点击一个可点击的元素时,比如button,我们需要得到用户点击的元素的信息,包含:用户点击了哪个元素、用户点击的那个元素的宽、高、位置信息等。得到了button的信息后,我就可以确定水波纹的范围,然后通过layout进行重绘去绘制水波纹,这样水波纹效果就实现了,当然,这只是大概步骤,中间还是有一些细节需要处理的。
layout的选取
既然我们打算实现一个自定义layout,那我们要选取那个layout呢,LinearLayout、RelativeLayout、FrameLayout?我这里选用LinearLayout。为什么呢?也许有人会问,不应该用RelativeLayout吗?因为RelativeLayout比较强大,可以实现复杂的布局,但LinearLayout和FrameLayout就不行。没错,RelativeLayout是强大,但是考虑到水波效果是通过频繁刷新layout来实现的,由于频繁重绘,因此,我们要考虑性能问题,RelativeLayout的性能是最差的(因为做的事情多),因为,为了性能,我们选择LinearLayout,至于FrameLayout,它功能太简单了,不太适合使用。当实现复杂布局的时候,我们可以在具有波纹效果的元素外部包裹LinearLayout,这样重绘的时候不至于有过重的任务。
根据上面的分析,我们定义如下的layout:
public class RevealLayout extends LinearLayout implements Runnable
实现过程
实现过程主要是如下几个问题的解决:
1. 如何得知用户点击了哪个元素
2. 如何取得被点击元素的信息
3. 如何通过layout进行重绘绘制水波纹
4. 如果延迟up事件的分发
下面一一进行分析
如何得知用户点击了哪个元素
这个问题好弄,为了得知用户点击了哪个元素(这个元素一般来说要是可点击的,否则是无意义的),我们要提前拦截所有的点击事件,于是,我们应该重写layout中的dispatchTouchEvent方法,注意,这里不推荐用onInterceptTouchEvent,因为onInterceptTouchEvent不是一直会被回调的,具体原因请参看我之前写的view系统解析系列。然后当用户点击的时候,会有一系列的down、move、up事件,我们要在down的时候来确定事件落在哪个元素上,down的元素就是用户点击的元素,当然为了严谨,我们还要判断up的时候是否也落在同一个元素上面,因为,系统click事件的判断规则就是:down和up同时落在同一个可点击的元素上。
[java] view plaincopy
@Override  
public boolean dispatchTouchEvent(MotionEvent event) {  
    int x = (int) event.getRawX();  
    int y = (int) event.getRawY();  
    int action = event.getAction();  
    if (action == MotionEvent.ACTION_DOWN) {  
        View touchTarget = getTouchTarget(this, x, y);  
        if (touchTarget.isClickable() && touchTarget.isEnabled()) {  
            mTouchTarget = touchTarget;  
            initParametersForChild(event, touchTarget);  
            postInvalidateDelayed(INVALIDATE_DURATION);  
        }  
    } else if (action == MotionEvent.ACTION_UP) {  
        mIsPressed = false;  
        postInvalidateDelayed(INVALIDATE_DURATION);  
        mDispatchUpTouchEventRunnable.event = event;  
        postDelayed(mDispatchUpTouchEventRunnable, 400);  
        return true;  
    } else if (action == MotionEvent.ACTION_CANCEL) {  
        mIsPressed = false;  
        postInvalidateDelayed(INVALIDATE_DURATION);  
    }  
  
    return super.dispatchTouchEvent(event);  
}  
[html] view plaincopy
private View getTouchTarget(View view, int x, int y) {  
    View target = null;  
    ArrayList<View> TouchableViews = view.getTouchables();  
    for (View child : TouchableViews) {  
        if (isTouchPointInView(child, x, y)) {  
            target = child;  
            break;  
        }  
    }  
  
    return target;  
}  
  
private boolean isTouchPointInView(View view, int x, int y) {  
    int[] location = new int[2];  
    view.getLocationOnScreen(location);  
    int left = location[0];  
    int top = location[1];  
    int right = left + view.getMeasuredWidth();  
    int bottom = top + view.getMeasuredHeight();  
    if (view.isClickable() && y >= top && y <= bottom  
            && x >= left && x <= right) {  
        return true;  
    }  
    return false;  
}  
[java] view plaincopy
int[] location = new int[2];  
mTouchTarget.getLocationOnScreen(location);  
int left = location[0] - mLocationInScreen[0];  
int top = location[1] - mLocationInScreen[1];  
int right = left + mTouchTarget.getMeasuredWidth();  
int bottom = top + mTouchTarget.getMeasuredHeight();  
[java] view plaincopy
protected void dispatchDraw(Canvas canvas) {  
    super.dispatchDraw(canvas);  
    if (!mShouldDoAnimation || mTargetWidth <= 0 || mTouchTarget == null) {  
        return;  
    }  
  
    if (mRevealRadius > mMinBetweenWidthAndHeight / 2) {  
        mRevealRadius += mRevealRadiusGap * 4;  
    } else {  
        mRevealRadius += mRevealRadiusGap;  
    }  
    int[] location = new int[2];  
    mTouchTarget.getLocationOnScreen(location);  
    int left = location[0] - mLocationInScreen[0];  
    int top = location[1] - mLocationInScreen[1];  
    int right = left + mTouchTarget.getMeasuredWidth();  
    int bottom = top + mTouchTarget.getMeasuredHeight();  
  
    canvas.save();  
    canvas.clipRect(left, top, right, bottom);  
    canvas.drawCircle(mCenterX, mCenterY, mRevealRadius, mPaint);  
    canvas.restore();  
  
    if (mRevealRadius <= mMaxRevealRadius) {  
        postInvalidateDelayed(INVALIDATE_DURATION, left, top, right, bottom);  
    } else if (!mIsPressed) {  
        mShouldDoAnimation = false;  
        postInvalidateDelayed(INVALIDATE_DURATION, left, top, right, bottom);  
    }  
}  

[java] view plaincopy
else if (action == MotionEvent.ACTION_UP) {  
           mIsPressed = false;  
           postInvalidateDelayed(INVALIDATE_DURATION);  
           mDispatchUpTouchEventRunnable.event = event;  
           postDelayed(mDispatchUpTouchEventRunnable, 400);  
           return true;  
       }   
可以发现,当up的时候,我并没有直接走系统的分发流程,只是强行消耗点up事件然后再延迟分发,请看代码:
[java] view plaincopy
private class DispatchUpTouchEventRunnable implements Runnable {  
    public MotionEvent event;  
  
    @Override  
    public void run() {  
        if (mTouchTarget == null || !mTouchTarget.isEnabled()) {  
            return;  
        }  
  
        if (isTouchPointInView(mTouchTarget, (int)event.getRawX(), (int)event.getRawY())) {  
            mTouchTarget.dispatchTouchEvent(event);  
        }  
    }  
};  

0 个回复

您需要登录后才可以回帖 登录 | 加入黑马