一. View位置參數與座標分析
View是Android中所有控件的基類,是一種界面層的控件的一種抽象,代表一個控件,常見的獲取位置參數的方法有以下三種:
1.View的getTop(),getLeft(), getRight(),getBottom()
View的位置主要由它的四個頂點來決定,分別對應View的四個屬性:top,left,right,bottom,其中top是左上角縱座標 ,left是左上角橫座標,right是右下角橫座標,bottom是右下角縱座標。需要注意的是,這些座標都是相對於View的父容器來說的,因此它是一種相對座標。具體參數看下圖:
在Android中,座標系的原點在屏幕的左上角(不包括狀態欄與標題欄的部分),x軸和y軸的正方向分別爲向右和向下,這一點很重要。
線性佈局豎直排列,從上到下依次爲一個Button,一個包括TextView的線性佈局,寬度,高度,邊距的設置如圖所示。看看在Activity中怎麼獲取它的位置參數:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
mainBtn = (Button) findViewById(R.id.main_btn);
WindowManager windowManager = (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
int width = windowManager.getDefaultDisplay().getWidth();
int height = windowManager.getDefaultDisplay().getHeight();
Log.e("log", "屏幕寬度:" + width);
Log.e("log", "屏幕高度:" + height);
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
Log.e("log", "按鈕的getTop():" + mainBtn.getTop());
Log.e("log", "按鈕的getBottom():" + mainBtn.getBottom());
Log.e("log", "按鈕的getLeft():" + mainBtn.getLeft());
Log.e("log", "按鈕的getRight():" + mainBtn.getRight());
Log.e("log", "按鈕的getWidth(px):" + mainBtn.getWidth());
Log.e("log", "按鈕的getHeight(px):" + mainBtn.getHeight());
Log.e("log", "按鈕的getWidth(dp):" + px2dp(getApplicationContext(), mainBtn.getWidth()));
Log.e("log", "按鈕的getHeight(dp):" + px2dp(getApplicationContext(), mainBtn.getHeight()));
Log.e("log", "文字的getTop():" + mainTxt.getTop());
Log.e("log", "文字的getBottom():" + mainTxt.getBottom());
Log.e("log", "文字的getLeft():" + mainTxt.getLeft());
Log.e("log", "文字的getRight():" + mainTxt.getRight());
}
//px轉dp
public static int px2dp(Context context, float pxValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
}
獲取屏幕的高度與寬度,然後重寫Activity的onWindowFocusChanged方法,獲取View的位置參數。剛開始我把這些方法直接寫在onCreate中,獲取到的值都是0。後來才知道,width、height、top、left等屬性值是在Measure與Layout過程完成之後纔開始正確賦值的,而Measure與Layout都晚於onCreate方法執行,所以onCreate中根本就取不到值!
關於正確獲取組件的寬高,可參考以下這篇博文:
我們一起看一下測試結果:
這裏的單位默認都是px,從以上結果我們可以得到以下結論:
View的位置參數是相對於自身的父容器來說的,是相對座標可以看到測試文字的座標參數是相對於自己的父容器計算的。
View的寬高與座標的關係:
width = right - left
height = bottom - top
最後我將得到的寬高單位從(px)轉換成了(dp),可以看到大小與我們在xml中設置的一致,完美~
2.View的getLocationInWindow()
這個方法獲取的是View在當前窗口的絕對座標,看一下測試代碼:
int[] btnWindowInt = new int[2];
mainTxt.getLocationInWindow(btnWindowInt);
Log.e("log", "文字在當前窗口內的絕對橫座標:" + btnWindowInt[0]);
Log.e("log", "文字在當前窗口內的絕對縱座標:" + btnWindowInt[1]);
int[] llWindowInt = new int[2];
maniLinearLayout.getLocationInWindow(llWindowInt);
Log.e("log", "線性佈局在當前窗口內的絕對橫座標:" + llWindowInt[0]);
Log.e("log", "線性佈局在當前窗口內的絕對縱座標:" + llWindowInt[1]);
測試結果:
打印結果裏的線性佈局就是父線性佈局。可以看到,這個線性佈局的絕對縱座標包括了通知欄與狀態欄的高度,我們在實際項目運用這個方法的過程中,一定記得減去這個高度。因爲Android座標系的原點在屏幕的左上角(不包括狀態欄與標題欄的部分)。
3.MotionEvent的getX(),getY(),getRawX(),getRawY()
首先理解這四個參數的意義:
getX():獲取點擊事件相對控件左邊的x軸座標,即點擊事件距離控件左邊的距離
getY():獲取點擊事件相對控件頂邊的y軸座標,即點擊事件距離控件頂邊的距離
getRawX():獲取點擊事件相對整個屏幕左邊的x軸座標,即點擊事件距離整個屏幕左邊的距離
getRawY():獲取點擊事件相對整個屏幕頂邊的y軸座標,即點擊事件距離整個屏幕頂邊的距離
測試代碼:
還是之前的佈局,我們給測試文字添加一個觸摸監聽:
mainTxt.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
int xRaw = (int) event.getRawX();
int yRaw = (int) event.getRawY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e("log", "觸摸事件相對控件座標爲:" + x + "," + y);
Log.e("log", "觸摸事件相對屏幕座標爲:" + xRaw + "," + yRaw);
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
break;
}
return false;
}
});
然後點擊測試文字,看一下打印的結果:
那麼這種結果在可以滑動的控件中是怎樣的呢,比如ListView,RecyclerView。其實對於這種滑動的ViewGroup,我們在獲取ViewGroup的座標值時並不需要考慮它到底滑動了多少(實際滑動的我們應該看作是ViewGroup中的View在滑動)。獲取到的結果與這裏還是一樣的,具體應用案例可參考我之前一篇博客:
二. View的最小滑動距離
TouchSlop是系統所能識別出的最小的滑動距離,如果兩次滑動之間的距離小於這個常量,那麼系統就不認爲你是在進行滑動操作。
ViewConfiguration.get(getApplicationContext()).getScaledTouchSlop();
可通過以上方法獲取到這個常量,打印出來大小是8dp。當我們處理滑動時,可以利用這個常量來做一些過濾,比如當兩次滑動事件的距離小於這個值,我們就可以認爲沒有達到滑動距離的臨界值,因此可以認爲它們不是滑動的。這樣做可以有更好的用戶體驗。
三.View滑動全解析
目前Android中實現View的滑動可以分爲三種方式:
通過改變View的佈局參數使得View重新佈局從而實現滑動
通過scrollTo/scrollBy方法來實現View的滑動
通過動畫給View施加平移效果來實現滑動
這裏將一一解析這三種方式的用法與區別:
首先看一下我們測試的佈局文件
然後就是Activity裏的代碼:
private void init() {
testBtn = (Button) findViewById(R.id.second_btn);
textView = (TextView) findViewById(R.id.second_txt);
testBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.e("log", "測試文字的getTop():" + textView.getTop());
Log.e("log", "測試文字的getBottom():" + textView.getBottom());
Log.e("log", "測試文字的getLeft():" + textView.getLeft());
Log.e("log", "測試文字的getRight():" + textView.getRight());
}
});
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(getApplicationContext(), "測試文字的點擊事件", Toast.LENGTH_SHORT).show();
}
});
}
點擊按鈕的時候,滑動測試文字textView,打印textView的位置參數,並且監聽textView的點擊事件。
測試文字的初始位置參數如下所示:
通過添加不同的方法來滑動測試文字,並且打印位置參數。所有方法的測試結果的示例圖如下:
一起看看怎麼實現的:
1.通過改變View的佈局參數使得View重新佈局從而實現滑動
1.使用layout(int l, int t, int r, int b)方法重新佈局:
textView.layout(textView.getLeft() + 50, textView.getTop() + 50, textView.getRight() + 50, textView.getBottom() + 50);
測試文字位置參數改變:
測試文字點擊事件有效
2.使用offsetTopAndBottom(int offset)與offsetLeftAndRight(int offset)方法進行偏移:
textView.offsetTopAndBottom(50);
textView.offsetLeftAndRight(50);
測試文字位置參數改變:
測試文字點擊事件有效
3.使用LayoutParams方法動態修改佈局參數:
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) textView.getLayoutParams();
layoutParams.leftMargin = textView.getLeft() + 50;
layoutParams.topMargin = textView.getTop() + 50;
textView.setLayoutParams(layoutParams);
測試文字位置參數不變:
測試文字點擊事件有效
2.通過scrollTo/scrollBy方法來實現View的滑動
4.使用scrollTo(int x, int y)方法進行移動:
關於view的scrollTo方法,我在 RecyclerView學習(三)—-高仿知乎的側滑刪除 這篇博客中有很詳細的介紹,大家也可以參考啓艦大神的ListView滑動刪除實現之二——scrollTo、scrollBy詳解。
//佈局文件
<LinearLayout
android:id="@+id/second_ll"
android:layout_width="match_parent"
android:layout_height="300dp"
android:orientation="horizontal">
<TextView
android:id="@+id/second_txt"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@color/colorAccent"
android:gravity="center"
android:text="測試文字" />
</LinearLayout>
linearLayout = (LinearLayout) findViewById(R.id.second_ll);
linearLayout.scrollTo(-50, -50);
之所以加上一個父佈局,是因爲scrollTo/scrollBy方法只能滑動view的內容,並不能滑動view本身。
測試文字位置參數不變:
測試文字點擊事件有效
5.使用scrollBy(int x, int y)方法進行移動:
linearLayout.scrollBy(-50, -50);
測試文字位置參數不變:
測試文字點擊事件有效
3. 通過動畫給View施加平移效果來實現滑動
6.使用補間動畫實現view的移動:
TranslateAnimation translateAnimation = new TranslateAnimation(0, 200, 0, 200);
translateAnimation.setDuration(1000);
translateAnimation.setFillAfter(true);
textView.startAnimation(translateAnimation);
測試文字位置參數不變:
使用補間動畫實現的測試文字滑動,會導致測試文字的點擊事件無效,只有點擊原區域,事件纔會有效!!!
7.使用屬性動畫實現view的移動:
AnimatorSet set = new AnimatorSet();
set.playTogether(
ObjectAnimator.ofFloat(textView, "translationX", 0, 200),
ObjectAnimator.ofFloat(textView, "translationY", 0, 200)
);
set.start();
測試文字位置參數不變:
使用屬性動畫實現的測試文字滑動,點擊事件依然有效!!!
那麼綜合對比這三種方式,各自的特點是什麼呢:
通過改變View的佈局參數使得View重新佈局從而實現滑動 ,操作稍微複雜,適用於有交互的View
通過scrollTo/scrollBy方法來實現View的滑動,操作簡單,適合對View內容的滑動
通過動畫給View施加平移效果來實現滑動,操作簡單,主要適用於沒有交互的View和實現複雜的動畫效果