您好,登錄后才能下訂單哦!
在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對象出現。
在往下就是介紹比較具體的開發思路
首先我們要初始化一些變量,其中的多數代碼已經在上面做出介紹了
Java代碼
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),代碼如下:
Java代碼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)方法的重寫
Java代碼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()方法的代碼
Java代碼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中):
Java代碼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的擴展類,重載那些繪制、位置計算的方法打個日志出來一看自然就明白了,雖然這個方法很笨,但是很容易出效果的
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。