最近在研究DrawerArrow库的时候需要仔细查看三条线是怎么旋转的,无奈抽屉打开的速度太快,原生的抽屉布局实现了谷歌SDK的方法,并使用600ms这个常量作为打开和关闭抽屉的时间,所以我们如果要调试一些根据抽屉而运动的动画时需要寻找到一个办法时SDK中的抽屉打开速度可控。
第一次尝试
尝试打开DrawerLayout类,这个类包含用来打开和关闭导航抽屉的接口,因为一般在SDK里面都会包含一些方法来设置动画的时长,所以第一次尝试我们可以查阅DrawerLayout的javadoc来寻找duration一类的方法,可是很遗憾没有这些方法。所以我们要另寻他法。
第二次尝试
查看DrawerLayout的源代码去寻找设置动画的方法,因为一般设置动画的方法一定会有duration这类的时长参数。(如何下载并导入supportv4源代码)
审阅DrawerLayout.java的源代码 是通过openDrawer()和closeDrawer()来控制抽屉的开启和关闭。而在这个方法里又是通过mLeftDragger和mRightDragger来控制开关。
public void closeDrawer(View drawerView) {
if (!isDrawerView(drawerView)) {
throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer");
}
if (mFirstLayout) {
final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
lp.onScreen = 0.f;
lp.knownOpen = false;
} else {
if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) {
mLeftDragger.smoothSlideViewTo(drawerView, -drawerView.getWidth(), drawerView.getTop());
} else {
mRightDragger.smoothSlideViewTo(drawerView, getWidth(), drawerView.getTop());
}
}
invalidate();
}
在这里我们注意到有个smoothSlideViewTo的方法很像是我们要找的东东,点进去看看。
这里要说明的是mLeftDragger是ViewDragHelper.java的实例,ViewDragHelper负责抽屉开关的动画,下面是smoothSlideViewTo()方法
public boolean smoothSlideViewTo(View child, int finalLeft, int finalTop) {
mCapturedView = child;
mActivePointerId = INVALID_POINTER;
return forceSettleCapturedViewAt(finalLeft, finalTop, 0, 0);
}
这段代码没告诉我们太多,仅仅告诉我们要去返回的那个方法进一步查看。
private boolean forceSettleCapturedViewAt(int finalLeft, int finalTop, int xvel, int yvel) {
final int startLeft = mCapturedView.getLeft();
final int startTop = mCapturedView.getTop();
final int dx = finalLeft - startLeft;
final int dy = finalTop - startTop;
if (dx == 0 && dy == 0) {
// Nothing to do. Send callbacks, be done.
mScroller.abortAnimation();
setDragState(STATE_IDLE);
return false;
}
final int duration = computeSettleDuration(mCapturedView, dx, dy, xvel, yvel);
mScroller.startScroll(startLeft, startTop, dx, dy, duration);
setDragState(STATE_SETTLING);
return true;
}
哈哈,我们找到一个叫duration的值,点进去看看。
private int computeSettleDuration(View child, int dx, int dy, int xvel, int yvel) {
xvel = clampMag(xvel, (int) mMinVelocity, (int) mMaxVelocity);
yvel = clampMag(yvel, (int) mMinVelocity, (int) mMaxVelocity);
final int absDx = Math.abs(dx);
final int absDy = Math.abs(dy);
final int absXVel = Math.abs(xvel);
final int absYVel = Math.abs(yvel);
final int addedVel = absXVel + absYVel;
final int addedDistance = absDx + absDy;
final float xweight = xvel != 0 ? (float) absXVel / addedVel :
(float) absDx / addedDistance;
final float yweight = yvel != 0 ? (float) absYVel / addedVel :
(float) absDy / addedDistance;
int xduration = computeAxisDuration(dx, xvel, mCallback.getViewHorizontalDragRange(child));
int yduration = computeAxisDuration(dy, yvel, mCallback.getViewVerticalDragRange(child));
return (int) (xduration * xweight + yduration * yweight);
}
看起来xweight和yweight是根据距离像素算出来的,不好改变,那我们就改变xduration和yduration的值吧。继续点击computeAxisDuration()
private int computeAxisDuration(int delta, int velocity, int motionRange) {
if (delta == 0) {
return 0;
}
final int width = mParentView.getWidth();
final int halfWidth = width / 2;
final float distanceRatio = Math.min(1f, (float) Math.abs(delta) / width);
final float distance = halfWidth + halfWidth *
distanceInfluenceForSnapDuration(distanceRatio);
int duration;
velocity = Math.abs(velocity);
if (velocity > 0) {
duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
} else {
final float range = (float) Math.abs(delta) / motionRange;
duration = (int) ((range + 1) * BASE_SETTLE_DURATION);
}
return Math.min(duration, MAX_SETTLE_DURATION);
}
返回一个计算出的duration和常量中的较小者。那么我们看看这个常量是多少
private static final int MAX_SETTLE_DURATION = 600; // ms
好吧,既然知道可以通过这个常量来加快抽屉的速度那么我们可以通过反射来改变这个值。
Field field = ViewDrager.class.getDeclaredField("MAX_SETTLE_DURATION");
field.setAccessible(true);
field.set(null, 100); //设置动画时间为100ms
很遗憾,在模拟器上能用,但是在真机上不能用,可能是没有动态改变成功,也可能是MAX_SETTLE_DURATION被标记为Final变量所以不能改变。
所以我用反射把final去掉再试试。。。
Field field = ViewDragHelper.class.getDeclaredField("MAX_SETTLE_DURATION");
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); //去掉final标记
field.set(null, 0);
又一次很遗憾,还是没改变成功。
第三场尝试
既然不能用反射修改变量值,那我就吧源代码拷下啦自定义一下好了吧。
复制DrawerLayout.java和ViewDragHelper.java到本地工程,把improt 修改为本地。这样你就可以随心所欲的修改了。
我可以这样改
也可以这样改
本文参考以下内容:
国外某程序猿博客和stackoverflow。