轉載請以連接形式標明出處: 本文出自:103style的博客java
《Android開發藝術探索》 學習記錄android
base on Android-29
bash
能夠帶着如下問題來看本文:ide
咱們先來看看這兩個方法的源碼:函數
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();
}
}
}
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
複製代碼
咱們能夠看到 scrollBy
也是調用了 scrollTo
方法。佈局
首先咱們先看下 mScrollX 和 mScrollY 的示例圖: post
經過上圖,咱們能夠很明顯的看出:學習
當View的內容往左往上時,mScrollX 和 mScrollY 爲正。動畫
當View的內容往右往下時,mScrollX 和 mScrollY 爲負。ui
也就是說在View的座標系中, mScrollX、mScrollY 分別爲View的邊緣減去對應內容邊緣的大小。
而且 scrollTo 和 scrollBy 改變的是其內容的位置,而不是其在佈局中的位置!
咱們來看個示例:
//activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/bt_anim"
android:layout_width="160dp"
android:layout_height="160dp"
android:text="Anim"
android:textAllCaps="false" />
</LinearLayout>
複製代碼
//MainActivity.java
public class MainActivity extends AppCompatActivity {
private Button btAnim;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btAnim = findViewById(R.id.bt_anim);
btAnim.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
btAnim.scrollTo(100, 100);
}
});
}
}
複製代碼
點擊能夠明顯看到,內容往左上方移動了。
咱們把btAnim.scrollTo(100, 100);
改爲
btAnim.scrollTo(-100, -100);
看看,能夠看到內容往右下方移動了。
動畫這塊咱們後面會單獨具體介紹,這裏先簡單介紹下怎麼使用動畫來實現滑動。
還記得咱們在 View的基礎知識介紹 中說到的View的位置參數中的 translationX
、translationY
嗎?動畫實現滑動就是改變這個屬性的值。
下面經過補間動畫和屬性動畫來實現View的滑動:
// res/anim/translation.xml
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="2000"
android:fromXDelta="0"
android:fromYDelta="0"
android:toXDelta="400"
android:toYDelta="400" />
複製代碼
這個補間動畫的意思是 將 View 從 (0,0) 在 2s 內移動到 (400,400)。
屬性動畫的用法則爲: ObjectAnimator.ofFloat(view, "translationX", 0, 400).setDuration(2000).start();
意思是 將 view 的 translationX 屬性在兩秒內從 0 移動到 400.
//MainActivity.java
public class MainActivity extends AppCompatActivity {
private Button btAnim;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btAnim = findViewById(R.id.bt_anim);
btAnim.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Animation animation = AnimationUtils.loadAnimation(MainActivity.this, R.anim.translation);
btAnim.startAnimation(animation);
}
});
btAnim.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
ObjectAnimator
.ofFloat(btAnim, "translationX", 0, 400)
.setDuration(2000)
.start();
return true;
}
});
}
}
複製代碼
上面代碼中咱們經過監聽 btAnim
的點擊事件來觸發 補間動畫,監聽長按事件來觸發 屬性動畫。
這裏咱們須要注意的是:
補間動畫 實現的平移 實際上只是對View的影像作操做,並不會真正改變View的位置參數。
若是咱們添加 android:fillAfter="true"
的話,當動畫結束後,則會停在最後的位置。
此時你會發現一個問題,當咱們再次點擊View時,並不會觸發動畫效果,可是點擊以前的位置則會觸發。
咱們修改例子來看看。
// res/anim/translation.xml
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="2000"
android:fillAfter="true"
android:fromXDelta="0"
android:fromYDelta="0"
android:toXDelta="400"
android:toYDelta="400" />
複製代碼
//MainActivity.java
public class MainActivity extends AppCompatActivity {
private Button btAnim;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btAnim = findViewById(R.id.bt_anim);
btAnim.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
show();
Animation animation = AnimationUtils.loadAnimation(MainActivity.this, R.anim.translation);
btAnim.startAnimation(animation);
}
});
btAnim.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
show();
ObjectAnimator
.ofFloat(btAnim, "translationX", 0, 400)
.setDuration(2000)
.start();
return true;
}
});
}
private void show(){
Toast.makeText(this,"start anim", Toast.LENGTH_SHORT).show();
}
}
複製代碼
咱們能夠看到 補間動畫 完了以後,只有點擊以前所在位置才能觸發點擊事件,
而 屬性動畫 則只有點到View所在的位置纔會觸發長按事件。
是什麼緣由咱們後面在將動畫的時候再介紹吧。
改變佈局參數很簡單,就是改變其 LayoutParams,咱們能夠經過 View.getLayoutParams()
來獲取這個佈局參數,而後修改屬性, 再經過 setLayoutParams()
或者 requestLayout()
來從新佈局。
具體咱們來下面這個示例:
//MainActivity.java
public class MainActivity extends AppCompatActivity {
private Button btAnim;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btAnim = findViewById(R.id.bt_anim);
btAnim.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
changeLayoutParams();
}
});
}
private void changeLayoutParams() {
LinearLayout.LayoutParams llp = (LinearLayout.LayoutParams) btAnim.getLayoutParams();
llp.width += 100;
llp.height += 100;
llp.setMarginStart(llp.getMarginStart() + 50);
llp.topMargin += 50;
btAnim.setLayoutParams(llp);
// btAnim.requestLayout();
}
}
複製代碼
咱們經過每次點擊使其 寬高 增長 100px
, 左邊距 和 上邊距 增長 50px
,效果圖以下:
這裏咱們先來總結下上面三種實現滑動的方法:
經過效果圖,咱們能夠很明顯的看到 scollTo/scollBy 和 改變佈局參數 這兩種實現滑動的方法 效果比較生硬,用戶體驗不太好。 動畫 方式實現的效果則會體驗好不少。
下面咱們來介紹經過 Scroller 來實先動畫那樣用戶體驗相對較好的 的滑動效果。
咱們在 View的基礎知識介紹 中有介紹 Scroller 的用法,再從新回顧下:
view
的 computeScroll
方法;mScroller.startScroll()
來實現滑動。//TestScroller.java
public class TestScroller extends TextView {
Scroller mScroller;
public TestScroller(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mScroller = new Scroller(context);
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
public void smoothScrollTo(int destX, int destY) {
int scrollX = getScrollX();
int scrollY = getScrollY();
int deltaX = destX - scrollX;
int deltaY = destY - scrollY;
mScroller.startScroll(scrollX, scrollY, deltaX, deltaY, 1000);
invalidate();
}
}
複製代碼
//activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.lxk.viewdemo.TestScroller
android:id="@+id/tv"
android:layout_width="320dp"
android:layout_height="320dp"
android:layout_margin="8dp"
android:background="@color/colorAccent"
android:gravity="center"
android:padding="8dp"
android:text="Hello World!" />
</LinearLayout>
複製代碼
//MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TestScroller scroller = findViewById(R.id.tv);
scroller.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
scroller.smoothScrollTo(200, 200);
}
});
}
複製代碼
運行,能夠看到點擊以後,內容在 1s
內往左上方各平移了 200px
, 而且改變的也是View的內容。
首先咱們來看看 smoothScrollTo
中調用的 Scroller
的 startScroll
方法,咱們能夠看到它其實只是保存咱們傳入的參數。
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;
}
複製代碼
而後經過在 smoothScrollTo
調用 invalidate()
方法,經過 invalidate()
觸發重繪,來調用 computeScroll
方法,
而後經過Scroller.computeScrollOffset()
判斷狀態,
知足則經過 mScroller.getCurrX()
和 mScroller.getCurrY()
獲取當前的位置,
而後經過 scrollTo
實現滑動,
而後經過 postInvalidate()
來繼續觸發重繪。
咱們來看看 Scroller
的 computeScrollOffset()
方法:
//Scroller.java
public boolean computeScrollOffset() {
if (mFinished) {
return false;
}
int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
if (timePassed < mDuration) {
switch (mMode) {
case SCROLL_MODE:
//經過插值器來計算對應時間對應的值
final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
mCurrX = mStartX + Math.round(x * mDeltaX);
mCurrY = mStartY + Math.round(x * mDeltaY);
break;
...
}
} else {
mCurrX = mFinalX;
mCurrY = mFinalY;
mFinished = true;
}
return true;
}
複製代碼
咱們能夠看到裏面經過插值器來計算對應時間對應的 mCurrX
、mCurrY
:
mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
而後經過重繪來達到平滑滑動的效果。
插值器 屬於 動畫那塊的內容,咱們在將動畫的時候在具體介紹,暫時當它是一個數學函數就能夠了。
至此,咱們大體知道了 Scroller實現滑動的原理爲: 咱們經過 Scroller 的 startScroll()
來設置要滑動的位置, 而後經過 invalidate()
觸發重繪 來調用 View 的 computeScroll()
方法, 而後在 Scroller 的computeScrollOffset()
中 經過插值器計算 這個滑動時間中 每一個時間點對應的 目標距離, 而後再經過 scrollTo()
滑動這個時間點對應的距離, 而後繼續重繪到對應時間點來實現滑動。
因此實際上 Scroller
自己並不能實現View的滑動,他須要配合View的 computeScroll()
方法才能達到平滑滑動的效果。
scrollTo 和 scrollBy 改變是 View 的什麼屬性?
A:mScrollX 和 mScrollY,內容往上往左滑 這兩個值爲正, 反之爲負。
補間動畫和屬性動畫的使用?
A: 補間動畫: 經過 AnimationUtils.loadAnimation(context, R.anim.translation)
來獲取補間動畫,而後經過 view.startAnimation(animation)
來執行動畫,補間動畫進改變View的影像,並不改變其實際位置,因此點擊事件只有點擊原位置纔會響應。
屬性動畫:經過 ObjectAnimator.ofFloat(view, 對應屬性, 起始值, 結束值).setDuration(時間).start()
來實現。
如何改變 View 的LayoutParams ?
A:經過 View.getLayoutParams()
獲取LayoutParams,而後修改寬高、邊距等,再經過 setLayoutParams()
或者 requestLayout()
來從新佈局。
Scroller實現平滑滑動的原理?
A:問題解答標題 上面就有,就再也不贅述了。
若是以爲不錯的話,請幫忙點個讚唄。
以上
掃描下面的二維碼,關注個人公衆號 Android1024, 點關注,不迷路。
`