View基础知识
什么是View
view是所有控件的基类,是界面层控件的一种抽象,他代表了一个控件。
ViewGroup也继承View,ViewGroup里面包含多个控件,即一组View。
View位置参数
view的宽高和坐标的关系:
width = right - left;
height = bottom - top;
left、top、right、bottom其中top和left是View的左上角坐标,right和bottom是右下角坐标。这些坐标都是相对于父容器来说的,因此说它是一种相对坐标。
如何得到View的四个参数:left、top、right、bottom
- Left = getLeft();
- Right = getRight();
- Top = getTop();
- Bottom = getBottom();
除此之外3.0之后View还提供了四个比较重要的位置参数信息,X、Y、translationX、translationY,和基本位置信息一样,也提供了get/set方法。
X、Y也比较好理解,是View左上角在父容器中的坐标(在View没有平移的情况下X=left,Y=top)
translationX,translationY则是View**当前位置相对于初始化位置的偏移量**,也就是说,如果你的View创建之后,没有进行过相关平移操作,translationX和translationY的值是始终为0的。
实例:
布局文件
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv_test"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_marginLeft="100dp"
android:layout_marginTop="100dp"
android:background="#8d13ca"
android:text="Hello World" />
</RelativeLayout>
MainActivity
private void initViews() {
tvTest = (TextView) findViewById(R.id.tv_test);
tvTest.setOnClickListener(this);
}
@Override
public void onClick(View v) {
print();
tvTest.setX(100);
System.out.println("==============");
print();
}
private void print() {
int width = tvTest.getWidth();
int height = tvTest.getHeight();
float translationX = tvTest.getTranslationX();
float translationY = tvTest.getTranslationY();
float x = tvTest.getX();
float y = tvTest.getY();
int left = tvTest.getLeft();
int top = tvTest.getTop();
int bottom = tvTest.getBottom();
int right = tvTest.getRight();
System.out.println("width" + width);
System.out.println("height" + height);
System.out.println("translationX" + translationX);
System.out.println("translationY" + translationY);
System.out.println("x" + x);
System.out.println("y" + y);
System.out.println("left" + left);
System.out.println("top" + top);
System.out.println("bottom" + bottom);
System.out.println("right" + right);
}
结果:
结果你会发现,View位置改变了,但是View的四个参数:left、top、right、bottom并没有变化。仅仅只是translationX和X的值发生了变化。
可以简单理解这几个参数,top、left、right、bottom主要负责View大小的控制,通过源码可以看到
public final int getWidth() {
return mRight - mLeft;
}
public final int getHeight() {
return mBottom - mTop;
}
View的宽高是有top、left、right、bottom参数决定的
而X,Y和translationX,和translationY则负责View位置的改变。
例子中可以看到X和translationX发生了变化。
translationX变成了-100;
X怎样变成100的呢?
换算关系:
X = left + translationX;
Y = top + translationY;
MotionEvent和TouchSlop
MotionEvent
的一个重要概念,事件类型。事件类型就是指MotionEvent
对象所代表的动作。
int action = MotionEventCompat.getActionMasked(event);
switch(action) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
break;
}
MotionEvent
也可以获取也可以得到点击事件发生的x,y坐标;
两组方法:
* getX()/getY()
* getRawX()/getRawY()
getX()/getY()相对于当前View左上角的x和y坐标。
getRawX()/getRawY()相对于手机屏幕左上角的x和y坐标。
TouchSlop
系统所识别出的,被认为是滑动最小的距离。
通过ViewConfiguration.get(getContext).getScaledTouchSlop();获取这个常量。
View的滑动
三中方式实现View滑动
使用scrollTo/scrollBy
mScrollX和mScrollY单位为像素。
scrollTo(x,y)移动到一个具体的坐标点(x,y)
scrollBy(dx,dy)表示移动的增量为dx,dy
当你点击View,这个View并没有移动,移动的是View里面的content;
如果在ViewGroup中,移动的将是所有的子View;
比如:TextView里面移动的是他的(content)文本,ImageView里面content是drawable对象
scrollBy移动需注意:
用scrollBy,你这样理解:手机屏幕相当于一个中间空的盖子,下面是一个很大的布,布里面是图像,当用scrollBy滑动时候,相当于手机(这个盖子在动),所以当你scrollBy(20,10)的时候,就相当于(手机)盖子向下走20、向左10.这时候相当于View向上减20,向右减10.
期是可以从源码看:
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
从左向右滑动,mScrollX为负值反之为正。
从上往下滑动,mScrollY为负值反之为正。
使用动画
属性动画比较好,位置也跟着动了,能够响应点击事件。
使用动画主要操作的是View的translationX和translationY属性。
改变布局参数LayoutParams
LayoutParams保存了一个View的布局参数,改变LayoutParams动态修改一个布局的位置参数。
首先通过这个View的getLayoutParams()获取这个View的LayoutParams。
再LayoutParams的leftMargin和rightMargin加上偏移量
最后setLayoutParams(layoutParams)就行了。
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) tvTest.getLayoutParams();
layoutParams.leftMargin += 100;
tvTest.setLayoutParams(layoutParams);
三种滑动总结
- scrollTo/scrollBy:操作简单,适合对View的内容滑动
- 动画:操作简单,适用于没有交互的View和实现复杂的动画效果
- 改变布局参数:操作稍微复杂,适用于有交互的View
弹性滑动
View的滑动不实现弹性滑动,这时候会很生硬,用户体验很差。
实现思想:一次大的滑动分成若干次小的滑动,并在一定时间内完成。
实现方式很多:Scroller、Handler#postDelayed以及Thread#sleep
Scroller
Scroller是弹性滑动对象,用于实现View的弹性滑动,但是Scroller本身无法让View弹性滑动,需要和View 的computeScroll方法配合才能实现。
使用:
* 创建一个Scroller对象
* 重写computrScroll(),实现模拟滑动
public class DragView5 extends View {
private int lastX;
private int lastY;
private Scroller mScroller;
public DragView5(Context context) {
super(context);
ininView(context);
}
public DragView5(Context context, AttributeSet attrs) {
super(context, attrs);
ininView(context);
}
public DragView5(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
ininView(context);
}
private void ininView(Context context) {
setBackgroundColor(Color.BLUE);
// 初始化Scroller
mScroller = new Scroller(context);
}
@Override
public void computeScroll() {
super.computeScroll();
// 判断Scroller是否执行完毕
if (mScroller.computeScrollOffset()) {
((View) getParent()).scrollTo(
mScroller.getCurrX(),
mScroller.getCurrY());
// 通过重绘来不断调用computeScroll
invalidate();
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = (int) event.getX();
lastY = (int) event.getY();
break;
case MotionEvent.ACTION_MOVE:
int offsetX = x - lastX;
int offsetY = y - lastY;
((View) getParent()).scrollBy(-offsetX, -offsetY);
break;
case MotionEvent.ACTION_UP:
// 手指离开时,执行滑动过程
View viewGroup = ((View) getParent());
mScroller.startScroll(
viewGroup.getScrollX(),
viewGroup.getScrollY(),
-viewGroup.getScrollX(),
-viewGroup.getScrollY());
invalidate();
break;
}
return true;
}
}
通过动画
比如让一个View的内容在100ms向左移动100像素
ObjectAnimator.ofFloat(tvTest, "translationX", 0, 100).setDuration(100).start();
使用延时策略
具体说可以使用Handler或者View的postDelayed方法,也可以使用线程的sleep。
postDelayed可以通过它来延时发送一个消息,然后在消息中进行View滑动(接连不断的发送这种延时消息,就可以实现弹性滑动)
sleep通过while循环中不断地滑动View和sleep,就可以实现弹性滑动。