View
1.View简介
1.View是Android所有控件的基类,同ViewGroup也是继承自View。
2.View的层级关系图:
2.视图坐标系
View的获取方法
getHeight():获取View自身高度
getWidth():获取View自身宽度
getTop():获取View自身顶边到其父布局顶边的距离
getLeft():获取View自身左边到其父布局左边的距离
getRight():获取View自身右边到其父布局左边的距离
getBottom():获取View自身底边到其父布局顶边的距离
MotionEvent所提供的方法
getX():获取点击事件距离控件左边的距离,即视图坐标
getY():获取点击事件距离控件顶边的距离,即视图坐标
getRawX():获取点击事件距离整个屏幕左边距离,即绝对坐标
getRawY():获取点击事件距离整个屏幕顶边的的距离,即绝对坐标
3.View的滑动方法
Android中经常看到一些炫酷的效果,这些效果很多伴随着View的滑动。
1) layout()
使用layout方法会直接改变View的位置,其内部会把四个参数分别赋值给mLeft、mTop、mRight、mBottom四个变量,四个顶点的位置也就相应改变。
int mLeft = imageView.getLeft();//获取ImageView左上角初始横坐标
int mTop = imageView.getTop();//获取ImageView左上角初始纵坐标
int mRight = imageView.getRight();//获取ImageView右下角初始横坐标
int mBottom = imageView.getBottom();//获取ImageView右下角初始纵坐标
image.layout(mLeft + 300,mTop,mRight + 300,mBottom);//重新布局
2) offsetLeftAndRight()与offsetTopAndBottom()
imageView.offsetLeftAndRight(300); //将imageView沿水平正方向偏移300px
imageView.offsetTopAndBottom(300); //将iamgeView沿竖直正方向偏移300px
3)LayoutParams(改变布局参数)
LinearLayout.LayoutParams layoutParams= (LinearLayout.LayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);
4)使用动画
view动画
//这里用不同的方式去实现。
TranslateAnimation animation = new TranslateAnimation(0, 300, 0, 0);
animation.setDuration(1500);//设置动画时长
animation.setFillAfter(true);//让ImageView停留在动画结束的位置
imageView.startAnimation(animation);
属性动画
ObjectAnimator.ofFloat(imageView,"translationX",0,300).setDuration(1500).start();
5)scollTo与scollBy
linearLayout.scrollTo(-300,0);
linearLayout.scrollBy(-150,0);
linearLayout.scrollBy(-150,0);
6)Scroller
public class CustomView extends LinearLayout {
private static final String TAG = "Scroller";
private Scroller mScroller;
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
mScroller = new Scroller(context);
}
//调用此方法滚动到目标位置
public void smoothScrollTo(int fx, int fy) {
int dx = fx - mScroller.getFinalX();
int dy = fy - mScroller.getFinalY();
smoothScrollBy(dx, dy);
}
//调用此方法设置滚动的相对偏移
public void smoothScrollBy(int dx, int dy) {
//设置mScroller的滚动偏移量
mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy);
invalidate();//这里必须调用invalidate()才能保证computeScroll()会被调用,否则不一定会刷新界面,看不到滚动效果
}
@Override
public void computeScroll() {
//先判断mScroller滚动是否完成
if (mScroller.computeScrollOffset()) {
//这里调用View的scrollTo()完成实际的滚动
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
//必须调用该方法,否则不一定能看到滚动效果
postInvalidate();
}
super.computeScroll();
}
}
4.Android属性动画
1)优点
不再局限于View对象,无对象也可以进行动画处理
不再局限于四种基本变换:平移,旋转,缩放,透明度
可以灵活的操作任意对象属性,根据自己的业务来实现自己想要的结果
2)核心点
ObjectAnimator 对象动画
ValueAnimator 值动画
PropertyValueHolder 用于同时执行多个动画
TypeEvaluator 估值器
AnimatorSet 动画集合
Interpolator 差值器
ObjectAnimator
使用ObjectAnimator完成在一段时间内透明度的变化
/**
* ObjectAnimator基本使用继承子ValueAnimator
* 对对象v的alpha参数进行操作,alpha的值从1.0变到0.3
*
* @param v
*/
public void startObjectAnimatorAnim(View v) {
ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(v, "alpha", 1.0f, 0.3f);
//执行事件
alphaAnim.setDuration(1000);
//延迟
alphaAnim.setStartDelay(300);
alphaAnim.start();
}
ValueAnimator
使用ValueAnimator完成在一段事件内缩放
/**
* 在一段时间内生成连续的值完成view的缩放
* @param v
*/
public void startValueAnimatorAnim(final View v) {
//不改变属性大小,只在一段事件内生成连续的值
ValueAnimator animator = ValueAnimator.ofFloat(0f, 100f);
animator.setDuration(500);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//百分比对应的值
float value = (float) animation.getAnimatedValue();
Log.e("TAG", "onAnimationUpdate: " + value);
v.setScaleX(0.5f + value / 200);
v.setScaleY(0.5f + value / 200);
}
});
animator.start();
}
PropertyValueHolde
使用PropertyValueHolder完成上面的俩个动画同时执行
/**
* 一个动画实现多个效果的变换
*
* @param v
*/
public void startPropertyValueHolderAnim(View v) {
PropertyValuesHolder alphaProper = PropertyValuesHolder.ofFloat("alpha", 0.5f, 1f);
PropertyValuesHolder scaleXProper = PropertyValuesHolder.ofFloat("scaleX", 0.5f, 1f);
PropertyValuesHolder scaleYProper = PropertyValuesHolder.ofFloat("scaleY", 0.5f, 1f);
ValueAnimator animator = ObjectAnimator.ofPropertyValuesHolder(v, alphaProper, scaleXProper, scaleYProper);
animator.setDuration(500);
animator.start();
}
AnimatorSet
AnimatorSet多个动画按制定顺序执行
/**
* 执行多个动画并控制动画顺序
*
* @param v
*/
public void startAnimatorSet(View v) {
ObjectAnimator animator1 = ObjectAnimator.ofFloat(v, "translationX", 0f, 100f);
ObjectAnimator animator2 = ObjectAnimator.ofFloat(v, "alpha", 0f, 1f);
ObjectAnimator animator3 = ObjectAnimator.ofFloat(v, "scaleX", 0f, 1f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(500);
//动画1,2同时执行
animatorSet.play(animator1).with(animator2);
//动画2执行完成后执行动画3
animatorSet.play(animator3).after(animator2);
animatorSet.start();
}
animatorSet.play(animator1).with(animator2);动画1和动画2同时执行
animatorSet.play(animator3).after(animator2);动画3在动画2执行完成后执行
animatorSet.playSequentially(animator1,animator2,animator3)动画1,2,3按顺序执行
animatorSet.playTogether(animator1,animator2,animator3)三个动画同时执行
TypeEvaluator
估值器可以自定义变换规则,普通动画是匀速执行
/**
* 使用估值器实现重力下落
*
* @param v
*/
public void startEvaluator(final View v) {
ValueAnimator animator = new ValueAnimator();
animator.setDuration(3000);
animator.setObjectValues(new PointF(0, 0));
final PointF pointF = new PointF();
animator.setEvaluator(new TypeEvaluator() {
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
//fraction是运动中的匀速变化的值
//根据重力计算实际的运动y=vt=0.5*g*t*t
//g越大效果越明显
pointF.x = 100 * (fraction * 5);
pointF.y = 0.5f * 300f * (fraction * 5) * (fraction * 5);
return pointF;
}
});
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
PointF p = (PointF) animation.getAnimatedValue();
v.setX(p.x);
v.setY(p.y);
}
});
animator.start();
}
Interpolator
插值器已经定义好计算规则的估值器(API中已经定义好了算法)
//加速查值器,参数越大,速度越来越快
animator.setInterpolator(new AccelerateInterpolator(10));
//减速差值起,和上面相反
animator.setInterpolator(new DecelerateInterpolator(10));
//先加速后减速插值器
animator.setInterpolator(new AccelerateDecelerateInterpolator());
//张力值,默认为2,T越大,初始的偏移越大,而且速度越快
animator.setInterpolator(new AnticipateInterpolator(3));
//张力值tension,默认为2,张力越大,起始时和结束时的偏移越大
animator.setInterpolator(new AnticipateOvershootInterpolator(6));
//弹跳插值器
animator.setInterpolator(new BounceInterpolator());
//周期插值器
animator.setInterpolator(new CycleInterpolator(2));
//线性差值器,匀速
animator.setInterpolator(new LinearInterpolator());
5.从源码解析Scroller
Scroller类是滚动的一个封装类,可以实现View的平滑滚动效果,举个简单的例子,一个View从在我们指定的时间内从一个位置滚动到另外一个位置,我们利用Scroller类可以实现匀速滚动,可以先加速后减速,可以先减速后加速等等效果,而不是瞬间的移动的效果,所以Scroller可以帮我们实现很多滑动的效果。
6.从源码解析View的事件分发机制
1) 处理点击事件的方法
dispatchTouchEvent(MotionEvent ev):用来进行事件的分发
onInterceptTouchEvent(MotionEvent ev):用来进行事件的拦截,在dispatchTouchEvent()中调用,需要注意的是View没有提供该方法
onTouchEvent(MotionEvent ev):用来处理点击事件,在dispatchTouchEvent()方法中进行调用
2)点击事件分发的传递规则
点击事件由上而下的传递规则
对于根ViewGroup,点击事件首先传递给它的dispatchTouchEvent()方法,如果该ViewGroup的onInterceptTouchEvent()方法返回true,则表示它要拦截这个事件,这个事件就会交给它的onTouchEvent()方法处理,如果onInterceptTouchEvent()方法返回false,则表示它不拦截这个事件,则交给它的子元素的dispatchTouchEvent()来处理,如此的反复下去。如果传递给最底层的View,View是没有子View的,就会调用View的dispatchTouchEvent()方法,一般情况下最终会调用View的onTouchEvent()方法。
点击事件由下而上的传递规则
点击事件传给最底层的View,如果他的onTouchEvent()方法返回true,则事件由最底层的View消耗并处理了,如果返回false则表示该View不做处理,则传递给父View的onTouchEvent()处理,如果父View的onTouchEvent()仍旧返回返回false,则继续传递给改父View的父View处理,如此的反复下去。
7.从源码解析Activity的构成
从源码解析Activity的构成
1.Activity的结构分为三层,分别是:Activity、Window和View。
2.结构的创建过程分为三步:1. 创建Activity,并且创建与其相关的WindowManager和Window,对应着是Activity的attach方法的调用;2. 初始化DecorView和ContentView,对应着的是Activity的onCreate方法;3. 创建ViewRootImpl,并且将DecorView添加到ViewRootImpl中,同时触发View树的三大流程。
3.在generateLayout方法里面,会根据设置不同的flags来计算DecorView的大小和位置,计算逻辑在updateColorViews方法里面;还是根据设置不同的features方法来选择默认加载到DecorView中,比如说设置了NO_ACTION_BAR的features,就会加载不带ActionBar的布局。
图解Activity的构成
8.从源码解析View的measure流程
View的工作流程,就是measure、layout和draw。measure用来测量View的宽高,layout用来确定View的位置,draw则用来绘制View。这一讲我们来看看measure流程,measure流程分为View的measure流程和ViewGroup的measure流程,只不过ViewGroup的measure流程除了要完成自己的测量还要遍历去调用子元素的measure()方法。
9.从源码解析View的layout和draw流程
1)layout的重点
View的四个点的坐标,它的坐标不是相对屏幕的原点,而且相对于它的父布局来说的。 l 和 t 是子控件左边缘和上边缘相对于父类控件左边缘和上边缘的距离;
r 和 b是子控件右边缘和下边缘相对于父类控件左边缘和上边缘的距离。
在setFrame()方法里主要是用来设置View的四个顶点的值,也就是mLeft 、mTop、mRight和 mBottom的值。在调用setFrame()方法后,调用onLayout()方法
onLayout()方法没有去做什么,这个和onMeasure()方法类似,确定位置时根据不同的控件有不同的实现,所以在View和ViewGroup中均没有实现onLayout()方法。
在setChildFrame()方法中调用子元素的layout()方法来确定自己的位置。我们看到childTop这个值是逐渐增大的,这是为了在垂直方向,子元素是一个接一个排列的而不是重叠的。
2)draw的重点
如果有设置背景,则绘制背景
保存canvas层
绘制自身内容
如果有子元素则绘制子元素
绘制效果
绘制装饰品(scrollbars)
10.自定义View
1)简介
自定义View按照笔者的划分,分为两大类,一种是自定义View,一种是自定义ViewGroup;其中自定义View又分为继承View和继承系统控件两种。这篇文章首先先了解下两大类的其中一种:自定义View。
2)继承系统控件的自定义View
这种自定义View在系统控件的基础上进行拓展,一般是添加新的功能或者修改显示的效果,一般情况下我们在onDraw()方法中进行处理。
3) 继承View的自定义View
不只是要实现onDraw()方法,而且在实现过程中还要考虑到wrap_content属性以及padding属性的设置;为了方便配置自己的自定义View还会对外提供自定义的属性,另外如果要改变触控的逻辑,还要重写onTouchEvent()等触控事件的方法。
11.自定义组合控件
自定义组合控件就是多个控件组合起来成为一个新的控件,主要用来解决多次重复的使用同一类型的布局。
1)组合控件的xml布局
2)组合控件的Java代码
3)自定义属性
4)xml中引用组合控件
5)调用组合控件
12.自定义ViewGroup
1)继承ViewGroup
要实现自定义的ViewGroup,首先要继承ViewGroup并调用父类构造方法,实现抽象方法等。
2)对wrap_content属性进行处理
3)实现onLayout
布局子元素,因为每一种布局方式子View的布局都是不同的,所以这个是ViewGroup唯一一个抽象方法,需要我们自己去实现
4)处理滑动冲突
ViewGroup是水平滑动,如果里面是ListView,则ListView是垂直滑动,如果我们检测到的滑动方向是水平的话,就让父View拦截用来进行View的滑动切换
5)弹性滑动到其他页面
进入onTouchEvent事件,然后我们需要进行滑动切换页面,这里需要用到Scroller。
6)快速滑动到其他页面
我们不只滑动超过一半才切换到上/下一个页面,如果滑动速度很快的话,我们也可以判定为用户想要滑动到其他页面,这样的体验也是好的。 这部分也是在onTouchEvent中的ACTION_UP部分:
这里又需要用到VelocityTracker,它用来测试滑动速度的。使用方法也很简单,首先在构造函数中进行初始化,接着改写onTouchEvent部分。
7)再次触摸屏幕阻止页面继续滑动
当我们快速向左滑动切换到下一个页面的情况,在手指释放以后,页面会弹性滑动到下一个页面,可能需要一秒才完成滑动,这个时间内,我们再次触摸屏幕,希望能拦截这次滑动,然后再次去操作页面。
要实现在弹性滑动过程中再次触摸拦截,肯定要在onInterceptTouchEvent中的ACTION_DOWN中去判断,如果在ACTION_DOWN的时候,scroller还没有完成,说明上一次的滑动还正在进行中,则直接中断scrolle。
8)应用HorizontalView
首先我们在主布局中引用HorizontalView,它作为父容器,里面有两个ListView,接着在代码中为ListView填加数据,最后贴上HorizontalView的源码。