2016年11月04日 21:55:04 csdn
讀完這篇博客可以實現:
1.scrollview從任意位置通過慣性滑動到任意位置
2.獲取手離開屏幕后慣性滑動的距離(時間也可以)
3.既然可以控制慣性滑動了,那么有時候慣性滑動造成的各種被重復觸發事件導致的bug也就可以解決了。
在scrollview中重寫fling()方法,他的參數是用戶滑動時,松開手的那一瞬間的初速度,他決定了view慣性滑動的時間和距離,這個參數可以根據需求傳入任意值,從而達到各種效果。
傳入你要到達的位置(scrollY),計算出需要的初速度(這個初速度可以在上面那條中用)
/**
* 通過目標y得到需要的初速度 (指頭向上滑,需要的初速度)
* @param endY
* @return
* @author xuekai
*/
public int getVelocityY(int endY){
int signum=-1;//注意:如果指頭上滑,他肯定是-1,如果下滑,把這里改成1,我的需求肯定是上滑,所以寫死了
double dis=(endY-upY)*signum;
double g= Math.log(dis/ViewConfiguration.getScrollFriction()/ (SensorManager.GRAVITY_EARTH // g (m/s^2)
* 39.37f // inch/meter
* (context.getResources().getDisplayMetrics().density * 160.0f)
* 0.84f))* Math.log(0.9)*((float) (Math.log(0.78) / Math.log(0.9)) - 1.0)/(Math.log(0.78) );
return (int) (Math.exp(g)/0.35f*(ViewConfiguration.getScrollFriction() * SensorManager.GRAVITY_EARTH
* 39.37f // inch/meter
* (context.getResources().getDisplayMetrics().density * 160.0f)
* 0.84f));
}
- 通過上面第一條中的初速度可以計算出它將會滑動的距離(scrollY)
/**
*
* @param velocityY 初速度
* @return 滑動的距離
* @author xuekai
*/
public double getDis(int velocityY) {
final double l = getG(velocityY);
final double decelMinusOne = (float) (Math.log(0.78) / Math.log(0.9)) - 1.0;
return ViewConfiguration.getScrollFriction() * (SensorManager.GRAVITY_EARTH // g (m/s^2)
* 39.37f // inch/meter
* (context.getResources().getDisplayMetrics().density * 160.0f)
* 0.84f) * Math.exp((float) (Math.log(0.78) / Math.log(0.9)) / decelMinusOne * l);
}
public double getG(int velocityY) {
return Math.log(0.35f * Math.abs(velocityY) / (ViewConfiguration.getScrollFriction() * SensorManager.GRAVITY_EARTH
* 39.37f // inch/meter
* (context.getResources().getDisplayMetrics().density * 160.0f)
* 0.84f));
}
我是準備利用scrollview實現一個下拉刷新的功能,scrollview初始狀態下讓他的向上滾動1000(假設值)。然后在onTouch事件中做處理:只要是觸發action_up,并且scrollY小于1000,就自動滾動到1000,同時,在onScrollChanged中也做一個判斷,當處于抬起狀態(在action_down和action_up中設置boolean)并且scrollY小于1000,也滾動到1000.這樣一個簡單地下拉刷新實現了。
可是scrollTo這個方法會讓它瞬間到了某個位置,沒有下拉刷新中緩慢上升的效果,怎么辦呢? 好說,值動畫嘛?。?!
寫完了,但是發現沒效果,原因是松開手之后觸發了action_up,立即彈回去了。去掉它,只用onScrollChanged,又會受到慣性滑動的影響(慣性滑動的時候,已經是抬起狀態了。)
看源碼發現ScrollView中有一個方法 fling(int velocityY)
/**
* Fling the scroll view
*
* @param velocityY The initial velocity in the Y direction. Positive
* numbers mean that the finger/cursor is moving down the screen,
* which means we want to scroll towards the top.
*/
public void fling(int velocityY) {
if (getChildCount() > 0) {
int height = getHeight() - mPaddingBottom - mPaddingTop;
int bottom = getChildAt(0).getHeight();
mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0,
Math.max(0, bottom - height), 0, height/2);
if (mFlingStrictSpan == null) {
mFlingStrictSpan = StrictMode.enterCriticalSpan("ScrollView-fling");
}
postInvalidateOnAnimation();
}
}
這個參數是松手那一瞬間的加速度。由于我的需求中,手指上滑不會出現這樣的問題,下滑才會,于是產生了這樣的代碼(在自定義scrollview中重寫它)
@Override
public void fling(int velocityY) {
if (velocityY < 0) {
super.fling(0);
return;
} else {
super.fling(velocityY);
}
}
這樣的話問題解決了,并且在上滑的時候也有慣性滑動,但是上滑就變得死氣沉沉,因為上滑的初速度都是0了,這咋行,繼續看。
在上面那個方法里調用了
mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0,
Math.max(0, bottom - height), 0, height/2);
點進去看
public void fling(int startX, int startY, int velocityX, int velocityY,
int minX, int maxX, int minY, int maxY, int overX, int overY) {
// Continue a scroll or fling in progress
if (mFlywheel && !isFinished()) {
float oldVelocityX = mScrollerX.mCurrVelocity;
float oldVelocityY = mScrollerY.mCurrVelocity;
if (Math.signum(velocityX) == Math.signum(Math.signum(velocityY) == Math.signum(oldVelocityY)) {
velocityX += oldVelocityX;
velocityY += oldVelocityY;
}
}
mMode = FLING_MODE;
mScrollerX.fling(startX, velocityX, minX, maxX, overX);
mScrollerY.fling(startY, velocityY, minY, maxY, overY);
}
發現有一行代碼是正對Y方向的fling的
跟著繼續走
getSplineFlingDuration(velocity);
getSplineFlingDistance(velocity);
發現了這么兩個方法。這豈不是飛的距離,飛的時間都有了嗎,那這樣我就可以控制“飛行”了。
用反射,然而靜態內部類掉不到。照著寫一個吧。
public double getSplineFlingDistance(int velocityY) {
final double l = getG(velocityY);
final double decelMinusOne = (float) (Math.log(0.78) / Math.log(0.9)) - 1.0;
return ViewConfiguration.getScrollFriction() * (SensorManager.GRAVITY_EARTH // g (m/s^2)
* 39.37f // inch/meter
* (context.getResources().getDisplayMetrics().density * 160.0f)
* 0.84f) * Math.exp((float) (Math.log(0.78) / Math.log(0.9)) / decelMinusOne * l);
}
public double getG(int velocityY) {
return Math.log(0.35f * Math.abs(velocityY) / (ViewConfiguration.getScrollFriction() * SensorManager.GRAVITY_EARTH
* 39.37f // inch/meter
* (context.getResources().getDisplayMetrics().density * 160.0f)
* 0.84f));
}
這樣就能得到了,也就是你在每次松開滑動的手的時候,可以知道他將滑動到的位置??墒沁@有什么卵用(不過有的一些需求下有用,對于我沒用)。 正好反了,我想要通過我要滑動到的位置來計算需要的初速度。
于是開始嘗試逆著算,數學里這叫還原法,不過不一定能還原出來。經過一系列的百度數學函數意思,結合高中忘得差不多的數學水平,終于還原出來了。
/**
* 通過目標y得到需要的初速度 (指頭向上滑,需要的初速度)
* @param endY
* @return
*/
public int getVelocityY(int endY){
int signum=-1;//如果指頭上滑,他肯定是-1;
double dis=(endY-upY)*signum;
double g= Math.log(dis/ViewConfiguration.getScrollFriction()/ (SensorManager.GRAVITY_EARTH // g (m/s^2)
* 39.37f // inch/meter
* (context.getResources().getDisplayMetrics().density * 160.0f)
* 0.84f))* Math.log(0.9)*((float) (Math.log(0.78) / Math.log(0.9)) - 1.0)/(Math.log(0.78) );
return (int) (Math.exp(g)/0.35f*(ViewConfiguration.getScrollFriction() * SensorManager.GRAVITY_EARTH
* 39.37f // inch/meter
* (context.getResources().getDisplayMetrics().density * 160.0f)
* 0.84f));
}