在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)
這個方法從字面理解就是估算控件的尺寸大小了,關於這個方法的詳細說明引用一下另一位童鞋的文章http://www.eoeandroid.com/thread-102385-1-1.html,這裏就不詳細介紹了
下面開始介紹關於如何讓自定義的控件進行平滑的移動,並能夠根據手勢的情況產生慣性滑動的效果
先介紹一下開發這種滑動效果需要用到的各種工具類:
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();
}
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();
}
}
private void obtainVelocityTracker(MotionEvent event) {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
}
private void releaseVelocityTracker() {
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
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;
}
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();
}
}
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();
}
}
}