一.View
View是庞大的,对于一个APP来说,与用户的交互、将内容展现给用户的部分是重要而且必要的,View也是微小的,屏幕上的一个小部分,都可能是一个View,而我们UI学习的第一步,也是从最简单的Textview,Button开始的,到后来,我们可以试着去写一些自定义的View来实现效果,这就是需要我们很好的掌握View的机制和工作流程。
二.ViewGroup
平时我们用的布局空间XXXLayout是继承自ViewGroup,那么这个ViewGroup可以理解为View的组合,他可以包含很多ViewGroup和View,这也是布局嵌套的本质。
三.坐标系
Android系统中有两种坐标系,Android坐标系和View坐标系。
1.Android坐标系
屏幕左上角为原点,原点向右为X正方向,向下是Y正方向
2.View坐标系
View的坐标系与Android的坐标系并不冲突,它们是共同存在的,它可以用来更好的控制View
那么从图中我们可以看到计算一个View的宽和高的方法
int width = button.getRight() - button.getLeft();
int height = button.getBottom() - button.getTop();
注意这个代码不能在onCreate里面写,因为当时View还没有做好初始化,是测量不到宽高的,其中一个方法如下
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
int width = button.getRight() - button.getLeft();
int height = button.getBottom() - button.getTop();
}
onWindowFocusChanged含义就是view已经初始化完毕,这个时候去获取宽高就没问题了,但是要注意的是此方法会被调用很多次,当Activity的窗口失去或者得到焦点都会调用一次,也就是Activity onResume或onPause时都会调用。
那么这样做的思路是对的,不过不必要这么麻烦了,系统已经为我们做好这步工作了。
我们可以直接用getWidth()和getHeight()来获取view宽高,得到的效果是一样的,我们可以直接看一下SDK源码。
AS查看源代码的方法:http://blog.csdn.net/glc_csdn/article/details/53993903
/**
* Return the width of the your view.
*
* @return The width of your view, in pixels.
*/
@ViewDebug.ExportedProperty(category = "layout")
public final int getWidth() {
return mRight - mLeft;
}
/**
* Return the height of your view.
*
* @return The height of your view, in pixels.
*/
@ViewDebug.ExportedProperty(category = "layout")
public final int getHeight() {
return mBottom - mTop;
}
同时我们看到还有好多方法
getTop() : 获取View顶部到父布局顶距离
getLeft() : 获取View左部到父布局左距离
getRight() : 获取View右部到父布局左距离
getBottom() : 获取View底部到父布局底距离
MotionEvent是为我们触摸点提供的方法,
getX():点击事件距离控件左边的距离
getY():点击事件距离控件顶边的距离
getRawX():点击事件距离父布局左边的距离
getRawY():点击事件距离父布局顶边的距离
View滑动
滑动实现思路是:当点击事件传到View后,系统记录触摸点坐标,手指移动系统记录移动后坐标并计算偏移量,通过偏移量来修改View坐标。
下面是几种View滑动方式
1.layout() 方法
我们来实现一个view滑动的效果
实现自定义View并在xml里面引用它
public class CustomView extends View {
private int lastX;
private int lastY;
public CustomView(Context context) {
super(context);
}
public CustomView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
//触摸事件
@Override
public boolean onTouchEvent(MotionEvent event) {
//获取事件的横纵坐标(相对于View)
int x = (int) event.getX();
int y = (int) event.getY();
//事件的监听
switch (event.getAction()){
//按下的时候记录触摸点的值
case MotionEvent.ACTION_DOWN:
lastX = x;
lastY = y;
break;
//移动过程中不断重绘
case MotionEvent.ACTION_MOVE:
//计算偏移量
int offsetX = x - lastX;
int offsetY = y - lastY;
//重新布局
layout(getLeft()+offsetX,getTop()+offsetY,getRight()+offsetX,getBottom()+offsetY);
break;
}
return true;
}
}
<com.surine.viewlearn.CustomView
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@color/colorPrimary"
/>
具体效果图,这里我放了4个自定义的view里面
2.offsetLeftAndRight()与offsetTopAndBottom()
这两个方法的效果是和layout一样的,但是他们可以直接使用偏移量
//偏移操作
offsetLeftAndRight(offsetX);
offsetTopAndBottom(offsetY);
3.LayoutParams(布局参数)
这个东西在调整view的宽高度,margin等中经常用到,那么我们换个思维,移动也就是在改变view的margin(对于父布局),具体的代码:
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getLeft() + offsetY;
setLayoutParams(layoutParams);
这里用了LinearLayout.LayoutParams,这是跟你的父布局的类型来决定,所以相对其他方法比较麻烦一点。
4.Animation
一个不是属于触摸移动的方式,使用动画,这就相对好理解一些,给view设置一个位移的动画,实现思路如下。
创建动画文件(res/anim/translate.xml)
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true"
>
<translate
android:fromXDelta="0"
android:toXDelta="300"
android:duration = "1000"
/>
</set>
启动动画
custom_view = findViewById(R.id.custom_view);
custom_view.setAnimation(AnimationUtils.loadAnimation(this,R.anim.translate));
当然我们用的是普通的动画,改变view的显示但并没有改变view的位置参数,所以对于点击事件来说不是很友好,那么这个问题在Android3以后的版本得到了解决,那就是属性动画。具体在后面的文章哦。
5.scrollTo与scrollBy
scrollTo是移动到某个坐标点,而scrollby是移动增量,那么他们的源码如下。
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
可以看到scrollby是调用了scrollTo方法的,传的参数是一个当前值+增量
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();
}
}
}
这里是scrollTo的方法体,mScrollX和mScrollY用于描述View的内容在水平方向或垂直方向滚动的距离。 那么在这里注意一下View的内容仅仅是指其中的东西,而不是View全部,例如textview的文字
那么我们要写具体的代码就是下面的这一行(在 case MotionEvent.ACTION_MOVE:标签内写)
((View)getParent()).scrollBy(-offsetX,-offsetY);
这里设置负数是因为视图与我们坐标轴的移动方向是相反的,可以想想中学实验显微镜的波片的移动和视野内的显示的例子。
View是Android中主要的一部分,掌握了View的基本操作才能更好的进阶!