View控件是安卓中非常重要的一个概念,下边将总结关于View工作原理的一些重要知识点(更新中…)
View 的位置左边参数
Scroller的使用
当我们使用View中的scrollTo/scrollBy方法来进行滑动时,过程是瞬间完成的,没有滑动的效果,为了能达到更好的用户体验,我们需要借助Scroller这个对象来完成,下边结合代码实例介绍
<!--MyView继承自TextView,内部添加一些文本方便看到滑动的效果-->
<com.wj.testdemo.MyView
android:id="@+id/my_view"
android:layout_width="match_parent"
android:layout_height="3000dp"
android:text="a\nb\nb\nb\nb\nb\nb\nb\nb\nb\nb"
android:background="#abcdef"/>
在MyView中添加如下代码,使用Scroller辅助View的内容平滑滑动
// 全局的Scroller对象
Scroller mScroller = new Scroller(getContext());
/**
* 平滑滑动的方法,使用这个方法滑动这个View的content
* @param destX 结果位置的x坐标
* @param destY 结果位置的y坐标
*/
public void smoothScrollTo(int destX, int destY){
// 通过当前位置计算得出结果位置坐标
int scrollX = getScrollX();
int scrollY = getScrollY();
int deltaX = destX - scrollX;
int deltaY = destY - scrollY;
// 这个方法主要给Scroller内部变量赋值
mScroller.startScroll(scrollX, scrollY, deltaX, deltaY, 1000);
invalidate(); // 使控件重绘
}
@Override
public void computeScroll() {
// 此处忽略了其父类TextView中的实现,重写
// 如果scroller没有达到结果位置,返回true,继续调用View的scrollTo方法,加入消息队列
if(mScroller.computeScrollOffset()){
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
以下是View中这个computeScroll方法的定义,可以看到它是空实现,具体实现由开发者来做。
/**
* Called by a parent to request that a child update its values for mScrollX
* and mScrollY if necessary. This will typically be done if the child is
* animating a scroll using a {@link android.widget.Scroller Scroller}
* object.
*/
public void computeScroll() {
}
以上注释的意思就是,子类在有必要更新mScrollX、mScrollY两个值时,会被父类调用,典型情况就是子类在使用Scroller做一个有动画效果的滚动时。上述写法是固定的。
使用Scroller的过程描述:
我们调用了startScroll方法,它的作用是给Scroller对象的参数赋值,来辅助记录动画的状态信息,然后具体事情是由invalidate方法来做的,他会导致View的重绘,然后在View的draw方法中又会调用那个我们自己实现的computeScroll方法,在这个方法中我们通过Scroller自身记录的状态参数得到当前View的坐标信息(通过调用computeScrollOffset获得,返回true说明,View动画还没有执行到我们预期的终点),然后接着我们调用View的scrollTo方法去实现View的滑动,并继续调用postInvalidate方法去调用invalidate方法重绘View,重复这个过程知道View滑动到我们给定的终点位置。
以下是上述提到方法的源码:
computeScrollOffset
/**
* Call this when you want to know the new location. If it returns true,
* the animation is not yet finished.
* 返回true,动画未完成,false,动画已完成
*/
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;
case FLING_MODE: // 惯性滑动模式
final float t = (float) timePassed / mDuration;
final int index = (int) (NB_SAMPLES * t);
float distanceCoef = 1.f;
float velocityCoef = 0.f;
if (index < NB_SAMPLES) {
final float t_inf = (float) index / NB_SAMPLES;
final float t_sup = (float) (index + 1) / NB_SAMPLES;
final float d_inf = SPLINE_POSITION[index];
final float d_sup = SPLINE_POSITION[index + 1];
velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
distanceCoef = d_inf + (t - t_inf) * velocityCoef;
}
mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;
mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
// Pin to mMinX <= mCurrX <= mMaxX
mCurrX = Math.min(mCurrX, mMaxX);
mCurrX = Math.max(mCurrX, mMinX);
mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
// Pin to mMinY <= mCurrY <= mMaxY
mCurrY = Math.min(mCurrY, mMaxY);
mCurrY = Math.max(mCurrY, mMinY);
if (mCurrX == mFinalX && mCurrY == mFinalY) {
mFinished = true;
}
break;
}
}
else {
// 已到达结束时间,直接将终点值设置为当前的坐标
mCurrX = mFinalX;
mCurrY = mFinalY;
mFinished = true;
}
return true;
}
改变LayoutParams参数实现View的滑动
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) click2Test.getLayoutParams();
if(params.width == ViewGroup.LayoutParams.WRAP_CONTENT){
params.width = 50;
}else {
params.width += 100;
}
params.leftMargin += 100;
click2Test.requestLayout();
// 下边这行代码同效果,注意如果view是包裹内容(wrap_content),当改变其width时,起初的width会是-2(wrap_content的值)
// click2Test.setLayoutParams(params);