View类中常用的api
getLeft()//得到view左上角的横坐标,也就是view的左边距离父布局左边的距离,得到的是View类中的mLeft变量的值
getTop()//得到view左上角的纵坐标,也就是view的上边距离父布局上边的距离,得到的是View类中的mTop变量的值
getRight()//得到view右下角的横坐标,也就是view的右边距离父布局左边的距离,得到的是View类中的mRight变量的值
getBottom()//得到view右下角的纵坐标,也就是view的下边距离父布局上边的距离,得到的是View类中的mBottom变量的值
getTranslationX()//得到view的左上角相对于父容器在x轴上的偏移量
getTranslationY()//得到view的左上角相对于父容器在y轴上的偏移量
getX()//得到view的左上角的横坐标,它=mLeft + getTranslationX(),原点是父view的左上角
getY()//得到view的左上角的纵坐标,它=mTop + getTranslationY(),原点是父view的左上角
MotionEvent类中常用的api
getX()//得到手指所点击的屏幕上的点的x坐标,所得的坐标的相对原点是自身的左上角
getY()//得到手指所点击的屏幕上的点的y坐标,所得的坐标的相对原点是自身的左上角
getRawX()得到手指所点击的屏幕上的点的x坐标,所得的坐标的相对原点是手机屏幕的左上角
getRawY()得到手指所点击的屏幕上的点的y坐标,所得的坐标的相对原点是手机屏幕的左上角
有用的知识点
1:最小滑动距离:ViewConfiguration.get(this).getScaledTouchSlop()//得到的是系统所能识别出的被认为是滑动的最小距离,换句话说,当手指在屏幕上滑动时,如果两次滑动之间的距离小于这个常量,那么系统就不认为你是进行滑动操作。这是一个常量,和设备有关,在源码中得到的值是8
2:速度追踪,用于追踪手指在滑动过程中的速度,包括水平和竖直方向的速度。在View的onTouchEvent方法中追踪当前事件的速度,代码如下所示:
VelocityTracker velocityTracker=VelocityTracker.obtain();
velocityTracker.addMovement(MotionEvent对象);
接着,当我们先知道当前的滑动速度时,可以采用如下方式获得当前的速度,在这一步中有亮点需要注意,第一点,获取速度之前必须先计算速度,即getXVelocity和getYVelocity这两个方法的前面必须要调用computeCurrentVelocity方法;第二点,这里的速度是指一段时间内手指所滑过的像素数,比如讲时间间隔设为1000ms时,在1s内,手指在水平方向从左向右滑过100像素,那么水平速度就是100。注意速度可以是负数,当手指从右往左滑动时,水平方向速度即为负值。速度的计算可以用如下公式来表示:
速度=(终点位置 - 起点位置)/ 时间段
computeCurrentVelocity这个方法的参数表示的是一个时间单元或者收时间间隔,它的单位是ms
velocityTracker.computeCurrentVelocity(1000);
int xVelocity= (int) velocityTracker.getXVelocity();
int yVelocity= (int) velocityTracker.getYVelocity();
最后,当不需要使用它的时候,需要调用clear方法来重置并回收内存:
velocityTracker.clear();
velocityTracker.recycle();
3:手势检测,用于辅助检测用户的单击,滑动,长按,双击等行为
GestureDetector gestureDetector=new GestureDetector(this, GestureDetector.OnGestureListener对象);
gestureDetector.setIsLongpressEnabled(false);//解决长按屏幕后无法拖动的现象
接着,接管目标View的onTouchEvent方法,在待监听View的onTouchEvent方法中添加如下实现:
boolean consume=gestureDetector.onTouchEvent(MotionEvent);
return consume;
做完了上面两步,我们就可以有选择的实现OnGestureListener和OnDoubleTapListener中的方法了,这两个接口中的方法介绍如下
4:常见的三种滑动方式
4.1:使用scrollTo/scrollBy,具体介绍请看https://www.jianshu.com/p/5b1be1f81c44;
4.2:使用属性动画,具体介绍请看https://www.jianshu.com/p/e0f2d2eff4a5;
4.3:改变布局参数;
针对以上三种滑动方式的总结如下:
scrollTo/scrollBy:适合对View内容的滑动;
属性动画:适用于实现复杂的动画效果;
改变布局参数:适用于简单的动画效果,并且有交互的View;
5:弹性滑动Scroller的用法,相当于一个插值器,提供一系列渐变值。
示例代码如下:
private TextView textView;
private Scroller scroller;
private LinearLayout linearLayout;
private Button button;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.ceshi);
linearLayout = findViewById(R.id.lin);
button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
scroller.startScroll(0, 0, 400, 600, 3000);
textView.invalidate();//这句代码是必须得有的,重新调用onDraw方法
}
});
scroller = new Scroller(this);
textView = new TextView(this) {
@Override
public void computeScroll() {
if (scroller.computeScrollOffset()) {
Log.d("CeShi", "textView.getLeft():" + scroller.getCurrX());
Log.d("CeShi", "textView.getTop():" + scroller.getCurrY());
//textView.scrollTo(scroller.getCurrX(), scroller.getCurrY());也可以使用scrollTo,改变textview内容的位置
ViewGroup.MarginLayoutParams marginLayoutParams=(ViewGroup.MarginLayoutParams)textView.getLayoutParams();
marginLayoutParams.leftMargin=scroller.getCurrX();
marginLayoutParams.topMargin=scroller.getCurrY();
textView.setLayoutParams(marginLayoutParams);
textView.invalidate();//这句代码是必须得有的,重新调用onDraw方法
}
}
};
textView.setText("我是滚动的textview");
textView.setScroller(scroller);
linearLayout.addView(textView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
}
6:View的事件分发机制,具体介绍请看https://www.jianshu.com/p/e64014e36d9b。另外注意点如下:
6.1:当一个View需要处理事件时,如果它设置了OnTouchListener,那么OnTouchListener中的onTouch方法会被回调。这时时间如何处理要看onTouch的返回值,如果返回false,则当前View的onTouchEvent方法会被调用;如果返回true,那么onTouchEvent方法将不会被调用。由此可见,给View设置的OnTouchListener,其优先级比onTouchEvent要高。在onTouchEvent方法中,如果当前设置的有OnClickListener,那么它的onClick方法会被调用。可以看出,平时我们常用的OnClickListener,其优先级最低,即处于事件传递的尾端。
6.2:当一个点击事件产生后,它的传递过程遵循如下顺序:Activity->Windows->View。考虑一种情况,如果一个View的OnTouchEvent返回false,那么它的父容器的onTouchEvent将会被调用,依次类推。如果所有的元素都不处理这个事件,那么这个事件将会最终传递给Activity处理,即Activity的onTouchEvent方法会被调用。换一种思路,加入点击事件是一个难题,这个难题最终被上级领导分给一个程序员去处理(这是事件分发过程),结果这个程序员搞不定(onTouchEvent返回了false),难题必须要解决,那只能交给上级去解决,就这样将难题一层层的向上抛。附带上activity,phonewindow,decorview,contentview之间的层次图:
6.3:View没有onInterceptTouchEvent方法,ViewGroup有。点击事件用MotionEvent来表示,当一个点击操作发生时,事件最先传递给当前Activity,由Activity的dispatchTouchEvent来进行事件派发,具体的工作是由Activity内部的Window来完成的。Window会将事件传递给decorview,decorview一般就是当前界面的底层容器(即setContentView所设置的View的父容器),通过Activity.getWindow.getDecorView()可以获得。
7:MeasureSpec
MeasureSpec代表一个32位int值,高2位代表SpecMode,低30位代表SpecSize。SpecMde有三类:unspecified,exactly,at_most。
8:当要获取某个View的宽/高时,获得的宽/高是0的原因是:View的measure过程和Activity的生命周期方法不是同步执行的,因此无法保证Activity执行了onCreate,onStart,onResume时某个View已经测量完毕了,如果View还没有测量完毕,那么获得宽/高就是0.解决方法有3个,如下所示:
8.1:Activity/View的onWindowFocusChanged:这个方法意味着View已经初始化完毕,宽/高已经准备好了。需要注意的是。onWindowFocusChanged会被调用多次,当Activity的窗口得到焦点和失去焦点均会被调用一次。具体来说,当Activity执行onResume和onPause时,onWindowFocusChanged均会被调用。示例代码如下:
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
Log.d("CeShi", "button.getWidth()+button.getHeight():" + (hasFocus + "," + button.getWidth() + "," + button.getHeight()));
}
}
8.2:view.post(runnable),示例代码如下:
button.post(new Runnable() {
@Override
public void run() {
Log.d("CeShi", "button.getWidth()+button.getHeight():" + (button.getWidth() + "," + button.getHeight()));
}
});
8.3:OnGlobalLayoutListener这个接口的作用是当View树的状态发生改变或者View树内部的View的可见性发生改变时,onGlobalLayout方法将会被回调,需要注意的是,伴随着View树的状态改变,onGlobalLayout会被调用多次,示例代码如下:
ViewTreeObserver viewTreeObserver=button.getViewTreeObserver();
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
Log.d("CeShi", "button.getWidth()+button.getHeight():" + (button.getWidth() + "," + button.getHeight()));
}
});
----------------------------------------------------------------------------------------------------------
android群英传中的知识点
1:getLocationOnScreen();得到view的左上角在屏幕中的坐标
imageView = findViewById(R.id.image);
imageView.post(new Runnable() {
@Override
public void run() {
int[] i = new int[2];
imageView.getLocationOnScreen(i);
Log.d("CeShi", "imageView的左上角在屏幕中的坐标为:" + i[0] + "," + i[1]);
//输出结果为:05-29 13:05:04.027 3558-3558/com.example.liang.arlvyou D/CeShi: imageView的左上角在屏幕中的坐标为:0,216
}
});
2:常用的api如下
3:实现滑动的7种方式
方式1:view的layout方式,示例代码如下:
myView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_MOVE:
myView.layout((int)event.getRawX()-myView.getWidth()/2,(int)event.getRawY()-myView.getHeight()/2,(int)event.getRawX()+myView.getWidth()/2,(int)event.getRawY()+myView.getHeight()/2);//将view的位置变换后都是以view的左上角为基点进行的变化,所以这里要减去或者加上宽或高的一半才位置差不多,当然可以直接通过getLeft()等方法来设置布局,那样更准确。
break;
}
return true;
}
});
方式2:view的offsetLeftAndRight/offsetTopAndBottom方式,效果和layout方式一样,示例代码如下:
myView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
int x=0;
int y=0;
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
x= (int) event.getX();
y= (int) event.getY();
break;
case MotionEvent.ACTION_MOVE:
myView.offsetLeftAndRight((int)event.getX()-x-myView.getWidth()/2);
myView.offsetTopAndBottom((int)event.getY()-y-myView.getHeight()/2);
break;
}
return true;
}
});
方式3:LyaoutParams,LyaoutParams保存了一个View的布局参数,通过getLayoutParams()获取LayoutParams时,需要根据View所在父布局的类型来设置不同的类型。比如这里将View放在LinearLayout中,那么就可以使用LinearLayoutLayoutParams,类似地,如果在RelativeLayout中,就要使用RelativeLayoutLayoutParams。当然,这一切的前提是你必须要有一个父布局,不然系统无法获取LayoutParams,示例代码如下:
myView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
int x=0;
int y=0;
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
x= (int) event.getX();
y= (int) event.getY();
break;
case MotionEvent.ACTION_MOVE:
LinearLayout.LayoutParams layoutParams= (LinearLayout.LayoutParams) myView.getLayoutParams();
layoutParams.leftMargin=myView.getLeft()+(int)event.getX()-x-myView.getWidth();
layoutParams.topMargin=myView.getTop()+(int)event.getY()-y-myView.getHeight();
myView.setLayoutParams(layoutParams);
break;
}
return true;
}
});
方式4:scrollTo和scrollBy,具体请查看
https://www.jianshu.com/p/5b1be1f81c44
方式5:Scroller,具体请查看
上方第5个知识点就是
方式6:属性动画,具体请查看
https://www.jianshu.com/p/e0f2d2eff4a5
方式7:ViewDragHelper
4:屏幕的尺寸信息
(1) 屏幕大小:指屏幕对角线的长度,通常使用“寸”来度量,例如4.7寸手机,5.5寸手机等。
(2) 分辨率:指手机屏幕的像素点个数,例如720*1280就是指屏幕的分辨率,指宽有720个像素点,高有1280个像素点。
(3) PPI(DPI):每英寸像素,它是由对角线的像素点数除以屏幕的大小得到的,通常达到400PPI就已经是非常高的屏幕密度了。
手机固定的DPI如下图所示:
5:独立像素密度dp
正是由于各种屏幕密度的不同,导致同样像素大小的长度,在不同密度的屏幕上显示长度不同。因为相同长度的屏幕,高密度的屏幕包含更多的像素点。android系统使用mdpi即密度值为160的屏幕作为标准,在这个屏幕上1px=1dp。其他屏幕则可以通过比例进行换算,例如同样是100dp的长度,在mdpi中为100px,而在hdpi中为150px。在mdpi中1dp=1px,在hdpi中1dp=1.5px,在xhdpi中1dp=2px,在xxdpi中1dp=3px。由此我们也可以得到各个分辨率中dp和px的换算比例,即ldpi:mdpi:hdpi:xhdpi:xxhdpi=3:4:6:8:12。
保证尺寸大小不变要用dp,保证文字大小不变要用sp。