View方面的东西,乍一眼看去,我真是满脸懵比,雾里看花般难受。
View是所有控件的基类,是一种界面层控件的抽象,代表着一个控件。连ViewGroup都是其子类。而ViewGroup可以直译为控件组,可以包含多个View。
一个View既可以表示一个控件,也可以是多个控件组成的一组控件。
一,View的基础知识
1.1 View的位置参数
一个矩形有四个点,而决定View的位置同样有四个点,分别是left,top,right,bottom。
其中left表示View的左上角距离左边的距离,top表示View的左上角距离上边的距离。
right表示View的右下角距离左边的距离,bottom表示View的右下角距离上边的距离。
当然,这些坐标相对于它的父容器而言的。
View的布局如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<asule.myview.ManualView
android:background="#123456"
android:layout_centerInParent="true"
android:layout_width="150dp"
android:layout_height="150dp" />
</RelativeLayout>
下面父容器指的是RelativeLayout
那么很容易的可以获得View的宽高
int width=right-left;
int height=bootom-top;
而得到这个四个位置参数的值,也很简单。
通过View的getLeft,getRight,getTop,getBottom方法。
在android3.0以后,View中又提供了几个位置参数:x,y,transactionX,transactionY四个参数。
x,y表示的是当前View在父容器中左上角的位置,而transactionX和transactionY表示的是View的左上角相对于父容器的偏移量。
所以android3.0以前,左上角的位置就是(left,top),而现在有了偏移量就变为(left+transactionX,top+transactionY)。
x=left+transactionX;
y=top+transactionY;
而默认情况下,transactionX和transactionY都为0。如下:
System.out.println("transactionX:" + getTranslationX());
System.out.println("transactionY:" + getTranslationY());
System.out: transactionX:0.0
System.out: transactionY:0.0
1.2 MotionEvent和TouchSlop
MotionEvent处理一些手指在屏幕上产生的事件,如
MotionEvent.ACTION_DOWN,手指按下。
MotionEvent.ACTION_MOVE,手指在屏幕上滑动。
MotionEvent.ACTION_UP,手指松开。
MotionEvent可以监听我们手指产生事件的同时,还可以得到点击事件位置的x和y的坐标。
int x= (int) event.getX();
int y= (int) event.getY();
int rawX=(int)event.getRawX();
int rawY= (int) event.getRawY();
getx/getY和getRawX/getRawY有很大的不同。
getx/getY得到的是点击事件的位置相对于这个View左上角的x,y坐标。
getRawX/getRawY得到的是点击事件的位置相对于手机屏幕左上角的x,y坐标。手机屏幕的x,y为(0,0)
TouchSlop是系统能够识别的可以被滑动的最小距离,如果你滑动的记录小于这个值,那么将不会进行滑动。会认为你滑动距离太短。
它的作用就是,在处理滑动的时候,可以进行对滑动距离的过滤。
得到系统认为的最小滑动距离:
int mTouchSlop=ViewConfiguration.get(getContext()).getScaledTouchSlop();
1.3 VelocityTracker,GestureDetector,Scroller
VelocityTracker的例子:
public boolean onTouchEvent(MotionEvent event) {
if (velocityTracker==null){
//创建VelocityTracker对象
velocityTracker = VelocityTracker.obtain();
}
//在onTouchEvent方法中追踪当前MotionEvent事件的速度
velocityTracker.addMovement(event);
//units的单位是毫秒,表示在多少毫秒内来计算的速度。假如你在50毫秒内完成了滑动,那么将计算不出来速度。
velocityTracker.computeCurrentVelocity(100);
/*
computeCurrentVelocity另一个重载方法computeCurrentVelocity(int units, float maxVelocity)
maxVelocity表示最大速率,如果速度大于了maxVelocity,显示的速度为maxVelocity。如果小于的话,那么就显示正常的速度。
velocityTracker.computeCurrentVelocity(1000, (float)20);
*/
xVelocity = (int) velocityTracker.getXVelocity();
yVelocity = (int) velocityTracker.getYVelocity();
/*
ACTION_DOWN和ACTION_UP的事件速度一般都为0,真正需要考虑的是ACTION_MOVE时的速度。
怎么计算速度?
速度=(终点位置-起点位置)/规定的毫秒值
本例中规定的毫秒值的是100毫秒。
从左往右滑动,
滑动的方向和x的正方向相同,那么x方向上的速度就为正值
如果向下倾斜,与y的正方向相同,那么y方向的速度为正值。
(android中的正方向是水平向右和垂直向下。)
*/
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
System.out.println("ACTION_DOWN");
System.out.println("xVelocity:" + xVelocity);
System.out.println("yVelocity:" + yVelocity);
break;
case MotionEvent.ACTION_MOVE:
System.out.println("ACTION_MOVE");
System.out.println("xVelocity:" + xVelocity);
System.out.println("yVelocity:" + yVelocity);
break;
case MotionEvent.ACTION_UP:
System.out.println("ACTION_UP");
System.out.println("xVelocity:" + xVelocity);
System.out.println("yVelocity:" + yVelocity);
break;
}
return true;
}
//当不需要使用VelocityTracker时重置并回收内存
velocityTracker.clear();
velocityTracker.recycle();
GestureDetector:
private MyGestureDetectorListener listener;
private GestureDetector detector;
private void init() {
//创建GestureDetector对象并实现OnGestureListener接口
listener = new MyGestureDetectorListener();
detector = new GestureDetector(listener);
}
/*
轻轻触碰屏幕并抬起
onDown---->onSingleTapUp
长按屏幕后抬起
onDown---->onShowPress---->onLongPress
手指在屏幕上滑动,最后松开
onDown---->onScroll(很多次调用)---->onFling
*/
class MyGestureDetectorListener implements GestureDetector.OnGestureListener{
//手指轻轻触碰屏幕触发一瞬间调用
@Override
public boolean onDown(MotionEvent e) {
System.out.println("onDown");
return true;
}
//手指轻轻触碰屏幕,但并没有松开或拖动
@Override
public void onShowPress(MotionEvent e) {
System.out.println("onShowPress");
}
//手指轻轻触碰屏幕,松开。表示单击行为
@Override
public boolean onSingleTapUp(MotionEvent e) {
System.out.println("onSingleTapUp");
return true;
}
//手指在按下并滑动
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
System.out.println("onScroll");
return true;
}
//手指长按
@Override
public void onLongPress(MotionEvent e) {
System.out.println("onLongPress");
}
//手指按下屏幕快速滑动后松开
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
System.out.println("onFling");
System.out.println("velocityX"+velocityX);
System.out.println("velocityY"+velocityY);
return true;
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//接管View的onTouchEvent方法
return detector.onTouchEvent(event);
}
Scroller:
弹性滑动对象,scroolTo和scroolBy两种方法的滑动都是瞬间完成的,没有过渡的滑动效果在。
而Scroller可以完成这个过渡效果。
它本身无法让View弹性滑动,它需要和View的computeScroll方法配合才能完成这个功能。
如何使用Scroller?会在下面介绍scroolTo和scroolBy时通过一个侧滑的demo来使用Scroller。
二,View的滑动
2.1 使用scrollTo和scrollBy
首先看源码:
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的基础上进行了移动。
scrollBy是绝对移动,它移动时会在上一个的位置上继续移动,而scrollTo却不是,它是直接移动到那个位置。
那么mScrollX和mScrollY是什么?
public final int getScrollX() {
return mScrollX;
}
public final int getScrollY() {
return mScrollY;
}
在view中可以通过getScrollX和getScrollY来获得。
mScrollX的值等于View的上边缘在水平方向上的值减去View内容的上边缘在水平方向上的值。
同理mScrollY的值等于View的下边缘在竖直方向上的值减去View内容的下边缘在竖直方向上的值。
单位是像素。
什么是View边缘,什么又是View内容的边缘?
View边缘指的是View的位置,由四个顶点组成,而View内容边缘指的是View中内容的边缘。
而scrollTo和scrollBy实现滑动,只能将View的内容进行移动。
一个概念的了解:
android的View视图是没有边界的,也就是说,我们在手机屏幕上看到的,是因为屏幕的限制,我们只能看到这么大。
既然这是前提,下面举例子来看。
public class MainActivity extends AppCompatActivity {
private ManualView manual;//自定义的TextView
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
manual = (ManualView) findViewById(R.id.manual);
int scrollX = manual.getScrollX();
int scrollY = manual.getScrollY();
System.out.println("默认情况下"+"ScrollX:"+scrollX + " --- ScrollY:" + scrollY);
findViewById(R.id.btn_scrollby).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
manual.scrollBy(-20, 0);
int scrollX = manual.getScrollX();
int scrollY = manual.getScrollY();
System.out.println("scrollby移动后" + "ScrollX:" + scrollX + " --- ScrollY:" + scrollY);
}
});
findViewById(R.id.btn_scrollto).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
manual.scrollTo(100,0);
int scrollX = manual.getScrollX();
int scrollY = manual.getScrollY();
System.out.println("scrollto移动后" + "ScrollX:" + scrollX + " --- ScrollY:" + scrollY);
}
});
}
}
运行后:
当scrollBy(-20, 0)后,就成为了下面这个样子:
前面提到的说每一个View的视图是没有边界的,而只是因为视图的边界超过了它的父容器(可以认为是被隐藏或覆盖),所以只会显示我们眼中看到的布局视图。
这个View没有边界的视图应该是下面这样子:
当我们scrollBy(-20,0)时,内容向右移动,滚动时不是以(0,0)作为参照,是调用scrollBy以及scrollTo方法的View自己作为参照。
为什么是向右,要知道View的布局并没有移动,移动的只是View的内容。
View布局的左上减去View内容的左上,此时此刻就是-20。
按照上面所说的View边缘和View内容边缘的值的差,可以得出此时的mScrollX为-20。
有时候在想scrollTo和scrollBy移动的只是View的内容,那么有的控件没有内容,又是怎么实现这个方法的呢?
2.2 使用动画完成滑动
使用平移动画或者属性动画。动画以后再深究。
2.3 改变布局参数
通过设置控件的LayoutParams,改变Margin值来达到View的移动。
貌似这种方法也想的出来啊。
RelativeLayout.LayoutParams params=
(RelativeLayout.LayoutParams) manual.getLayoutParams();
params.leftMargin+=100;
manual.setLayoutParams(params);
//或
manual.requestLayout();
2.4 各种滑动方法的对比
scrollTo和scrollBy方法可以很方便的实现滑动功能,但缺点是只能滑动View的内容,不可以滑动View本身。
使用动画的话,要注意属性动画只能使用到Android3.0以上,如果要向下兼容,还需要使用第三方的库。
改变布局参数,操作有些复杂,适用于有交互的View。