編輯:關於Android編程
1.View的位置參數

2.MotionEvent
手指觸摸屏幕後的一系列事件,包括ACTION_DOWN,ACTION_MOVE,ACTION_UP
3.TouchSlop
系統所能識別的被認為是滑動的最小距離,獲取方式為:
ViewConfiguration.get(getContext()).getScaledTouchSlop()
4.VelocityTracker速度追蹤
VelocityTracker的使用方式
//初始化
VelocityTracker mVelocityTracker = VelocityTracker.obtain();
//在onTouchEvent方法中
mVelocityTracker.addMovement(event);
//獲取速度
mVelocityTracker.computeCurrentVelocity(1000);
float xVelocity = mVelocityTracker.getXVelocity();
//重置和回收
mVelocityTracker.clear(); //一般在MotionEvent.ACTION_UP的時候調用
mVelocityTracker.recycle(); //一般在onDetachedFromWindow中調用
5.GestureDetector
GestureDetector用於輔助檢測用戶的單擊、滑動、長按、雙擊等行為
使用方法:
先創建GestureDetector 並實現onGestureListener接口,需要時還可以實現OnDoubleTapListener實現雙擊行為 GestureDetector mGestureDetector = new GestureDetector(this); //解決長按屏幕後無法拖動現象 mGestureDetector .setIsLongpressEnabled(false); 然後接管view的onTouchEvent方法,添加如下實現 return mGestureDetector.onTouchEvent(event);
做完以上步驟就可以有選擇的實現onGestureListener和OnDoubleTapListener的方法(具體實現這裡不詳細說明)
建議:如果只是監聽滑動相關的事件在onTouchEvent中實現;如果要監聽雙擊這種行為的話,那麼就使用GestureDetector。
6.Scroller
當使用View的scrollBy和scrollTo的時候,過程是瞬間完成的,用戶體驗差。因此,可以使用scroller來實現有過渡效果的滑動
使用方法
Scroller scroller = new Scroller(getContext());
//緩慢移動到指定位置
private void smoothScrollBy(int dx, int dy) {
int delta = dx - getScrollX();
//500ms內劃向dx
scroller.startScroll(getScrollX(), 0, delta, 0, 500);
invalidate();
}
@Override
public void computeScroll() {
if (scroller.computeScrollOffset()) {
scrollTo(scroller.getCurrX(), scroller.getCurrY());
postInvalidate();
}
}
7.View的滑動
(1)使用scrollTo/scrollBy
/**
* Set the scrolled position of your view. This will cause a call to
* {@link #onScrollChanged(int, int, int, int)} and the view will be
* invalidated.
* @param x the x position to scroll to
* @param y the y position to scroll to
*/
public void scrollTo(int x, int y) {
if (mScrollX != x || mScrollY != y) {
int oldX = mScrollX;
int oldY = mScrollY;
mScrollX = x;
mScrollY = y;
invalidateParentCaches();
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (!awakenScrollBars()) {
postInvalidateOnAnimation();
}
}
}
/**
* Move the scrolled position of your view. This will cause a call to
* {@link #onScrollChanged(int, int, int, int)} and the view will be
* invalidated.
* @param x the amount of pixels to scroll by horizontally
* @param y the amount of pixels to scroll by vertically
*/
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
可以看出,scrollBy其實調用了scrollTo函數。scrollTo滑動到絕對位置,scrollBy是相對位置。兩者只能改變view內容的位置,不能改變在布局中的位置。
(2)使用動畫
網上例子很多,不再說明
(3)改變布局參數
比如讓一個button向右平移100px可以用如下代碼
MarginLayoutParams params = (MarginLayoutParams)button.getLayoutParams();
params.width += 100;
params.leftMargin += 100;
button.requestLayout();
//或者button。setLayoutParams(params)
(4)各種滑動方式對比
使用scrollTo/scrollBy:操作簡單,適合對View內容的滑動
動畫:操作簡單,主要適合於沒有交互的view和實現復雜的動畫效果
改變布局參數:操作稍微復雜,適用於有交互的view
8.彈性滑動
(1)使用scroller
使用方法之前已經講過,現在看一下如何實現的。主要是startScroll和computeScrollOffset兩個函數
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
mMode = SCROLL_MODE;
mFinished = false;
mDuration = duration;
mStartTime = AnimationUtils.currentAnimationTimeMillis();
mStartX = startX;
mStartY = startY;
mFinalX = startX + dx;
mFinalY = startY + dy;
mDeltaX = dx;
mDeltaY = dy;
mDurationReciprocal = 1.0f / (float) mDuration;
// This controls the viscous fluid effect (how much of it)
mViscousFluidScale = 8.0f;
// must be set to 1.0 (used in viscousFluid())
mViscousFluidNormalize = 1.0f;
mViscousFluidNormalize = 1.0f / viscousFluid(1.0f);
}
在這個方法中我們只看到了對一些滾動的基本設置動作,比如設置滾動模式,開始時間,持續時間等等,並沒有任何對View的滾動操作
其實整個過程是這樣的:View重繪後會在draw方法中調用computeScroll,這是個空方法,要自己實現
@Override
public void computeScroll() {
if (scroller.computeScrollOffset()) {
scrollTo(scroller.getCurrX(), scroller.getCurrY());
postInvalidate();
}
}
在我們自己實現的方法中先調用scrollTo再調用postInvalidate進行第二次重繪,然後調用draw函數,這樣如此反復知道滑動結束
再看一下computeScrollOffset方法
public boolean computeScrollOffset() {
if (mFinished) {
return false;
}
int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
if (timePassed < mDuration) {
switch (mMode) {
case SCROLL_MODE:
float x = (float)timePassed * mDurationReciprocal;
if (mInterpolator == null)
x = viscousFluid(x);
else
x = mInterpolator.getInterpolation(x);
mCurrX = mStartX + Math.round(x * mDeltaX);
mCurrY = mStartY + Math.round(x * mDeltaY);
break;
case FLING_MODE:
float timePassedSeconds = timePassed / 1000.0f;
float distance = (mVelocity * timePassedSeconds)
- (mDeceleration * timePassedSeconds * timePassedSeconds / 2.0f);
mCurrX = mStartX + Math.round(distance * mCoeffX);
// Pin to mMinX <= mCurrX <= mMaxX
mCurrX = Math.min(mCurrX, mMaxX);
mCurrX = Math.max(mCurrX, mMinX);
mCurrY = mStartY + Math.round(distance * mCoeffY);
// Pin to mMinY <= mCurrY <= mMaxY
mCurrY = Math.min(mCurrY, mMaxY);
mCurrY = Math.max(mCurrY, mMinY);
break;
}
}
else {
mCurrX = mFinalX;
mCurrY = mFinalY;
mFinished = true;
}
return true;
}
它根據時間的流逝來計算當前的scrollX和scrollY的值
(2)動畫
(3)延時策略
通過handler的sendEmptyMessageDelayed方法
8.事件分發機制
9.滑動沖突
解決方法
1.外部攔截法:點擊事件都先經過父容器的攔截處理,如果父容器需要此事件就攔截,如果不需要就不攔截。該方法需要重寫父容器的onInterceptTouchEvent方法,在內部做相應的攔截即可,其他均不需要做修改。偽代碼如下:
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercepted = false;
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
intercepted = false;
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastXIntercept;
int deltaY = y - mLastYIntercept;
if (父容器需要攔截當前點擊事件的條件) {
intercepted = true;
} else {
intercepted = false;
}
break;
}
case MotionEvent.ACTION_UP: {
intercepted = false;
break;
}
default:
break;
}
mLastXIntercept = x;
mLastYIntercept = y;
return intercepted;
}
2.內部攔截法:父容器不攔截任何事件,所有的事件都傳遞給子元素,如果子元素需要此事件就直接消耗掉,否則就交給父容器來處理。這種方法和Android中的事件分發機制不一致,需要配合requestDisallowInterceptTouchEvent方法才能正常工作。我們需要重寫子元素的dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
getParent().requestDisallowInterceptTouchEvent(true);
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastX;
int deltaY = y - mLastY;
if (父容器需要點擊事件) {
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(event);
}
當然,不能攔截父容器的ACTION_DOWN事件
public boolean onInterceptTouchEvent(MotionEvent ev){
if(ev.getAction() == MotionEvent.ACTION_DOWN){
return false;
}else{
return true;
}
}
為什麼不能攔截呢?因為攔截了ACTION_DOWN,會導致所有時間都無法傳遞到子元素
看該文的最後,scrollview和listview的滑動沖突
我決定采用外部攔截法。因此,我需要自定義MyScrollView來繼承ScrollView並重寫onInterceptTouchEvent
關鍵代碼:
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercepted = false;
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e("ACTION_DOWN", "ACTION_DOWN");
intercepted = false;
break;
case MotionEvent.ACTION_MOVE:
Log.e("ACTION_MOVE", "ACTION_MOVE");
//getChildAt(0)得到的就是listview
if (y > getChildAt(0).getBottom()) {
intercepted = true;
} else {
intercepted = false;
}
break;
case MotionEvent.ACTION_UP:
Log.e("ACTION_UP", "ACTION_UP");
intercepted = false;
break;
}
Log.e("intercepted", "" + intercepted);
return intercepted;
}
這樣,完美解決
Android動畫之插值器(三)
本文從源碼的角度,來展開對動畫的深入解析,關於動畫基本用法,可查看Android動畫之入門篇(一),Android動畫之入門篇(二)。關於動畫有兩個非常重要
Android SlidingMenu 使用詳解
很多APP都有側滑菜單的功能,部分APP左右都是側滑菜單~SlidingMenu 這個開源項目可以很好幫助我們實現側滑功能,如果對SlidingMenu 還
Android--百度地圖開發(二)
顯示百度地圖百度地圖SDK為開發者提供了便捷的顯示百度地圖數據的接口,通過以下幾步操作,即可在您的應用中使用百度地圖數據:第一步:創建並配置工程(具體方法參見工程配置部分
Android體系架構詳解
本文講述的Android系統體系架構,是指應用層之下的整個系統內部的架構層級關系。而並非常說的4層架構:應用層,framework,運行庫與環境,Linux