参考Android进阶之光第三章
Android坐标系:左上角为(0,0),右为x轴正方向,下为y轴正方向
View坐标系
layout(getLeft() + disX, getTop() + disY,
getRight() + disX, getBottom() + disY);//1
//相当于对layout方法的一种封装
offsetLeftAndRight(disX);
offsetTopAndBottom(disY);//2
//改变布局参数(父控件为LinearLayout)
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft() + disX;
layoutParams.topMargin = getTop() + disY;
setLayoutParams(layoutParams);//3
ViewGroup.MarginLayoutParams layoutParams1 = (ViewGroup.MarginLayoutParams) getLayoutParams();
layoutParams1.leftMargin = getLeft() + disX;
layoutParams1.topMargin = getTop() + disY;
setLayoutParams(layoutParams1);//4
全部代码(一个View实现随手指滑动而滑动)
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);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
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 disX = x - lastX;
int disY = y - lastY;
layout(getLeft() + disX, getTop() + disY,
getRight() + disX, getBottom() + disY);//1
// offsetLeftAndRight(disX);
// offsetTopAndBottom(disY);//2
//
//
// LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();
// layoutParams.leftMargin = getLeft() + disX;
// layoutParams.topMargin = getTop() + disY;
// setLayoutParams(layoutParams);//3
//
// ViewGroup.MarginLayoutParams layoutParams1 = (ViewGroup.MarginLayoutParams) getLayoutParams();
// layoutParams1.leftMargin = getLeft() + disX;
// layoutParams1.topMargin = getTop() + disY;
// setLayoutParams(layoutParams1);//4
break;
}
return true;
}
}
((View)getParent()).scrollBy(-disX,-disY);
这里为什么要设置为负值呢?
在使用ScrollTo和ScrollBy时,过程为瞬间完成的,为了实现过程的滑动,我们要用Scroller+View的computeScroll()进行配合
初始化Scroller
public CustomView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mScroller = new Scroller(context);
}
系统会在绘制View的时候在draw()中调用该方法,在这个方法中,我们调用父类的scrollTo()方法并通过Scroller来不断获取当前的滚动值,每滚动一小段距离就调用invalidate()不断地进行重绘,重绘就会又调用该方法,这样就连贯起来实现了平滑移动的效果;
@Override
public void computeScroll() {
super.computeScroll();
if (mScroller.computeScrollOffset()) {
((View) getParent()).scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
invalidate();
}
}
这个是对外提供的一个方法,用来实现2秒内向右滑动一段距离
public void smoothScrollTo(int destX, int destY) {
int scrollX = getScrollX();
int delta = destX - scrollX;
mScroller.startScroll(scrollX, 0, delta, 0, 2000);
invalidate();
}
1.translatiionX和translationY: 平移
2.rotation旋转
3.PrivotX和PrivotY: 设定支点位置,默认支点为中心
4.alpha : 透明度
5.x和y :描述View的最终位置
实现点击旋转的效果
customView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e(TAG, "onClick: ");
if (customView.getRotation() == 0 || customView.getRotation() == 360) {
ObjectAnimator.ofFloat(customView, "rotation", 0, 180)
.setDuration(2000).start();
} else if (customView.getRotation() == 180) {
ObjectAnimator.ofFloat(customView, "rotation", 180, 360)
.setDuration(2000).start();
}
}
});
当多个动画效果时,使用一个set集合.with代表同时执行..after代表先执行哪一个动画
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(clickMe, "translationX", 0.0f, 200f, 0f);
ObjectAnimator objectAnimator1 = ObjectAnimator.ofFloat(clickMe,"alpha",1.0f,0.1f,1.0f);
ObjectAnimator objectAnimator2 = ObjectAnimator.ofFloat(clickMe,"scaleX",1.0f,0.8f,1.0f);
ObjectAnimator objectAnimator3 = ObjectAnimator.ofFloat(clickMe, "rotation", 0f, 180f, 360f);
AnimatorSet set = new AnimatorSet();
set.setDuration(2000);
set.play(objectAnimator).with(objectAnimator1).with(objectAnimator2).with(objectAnimator3);
set.start();
当点击事件产生后,事件首先会传递给当前的Activity,这会调用Activity的dispatchTouchEvent()方法,具体的事件处理工作都是交给PhoneWindow来完成的,然后PhoneWindow再把处理工作交给DecorView,之后再由DecorView将事件处理工作交给ViewGroup
事件分发的3个重要方法:
1.dispatchTouchEvent(MotionEvent ev)用于进行事件的分发
2.onInterceptTouchEvent(MotionEvent ev) 用于进行事件的拦截,在dispatchTouchEvent中调用,需要注意的是View中没有该方法;
3.onTouchEvent(MotionEvent ev) 用于处理点击事件,在dispatchTouchEvent中调用
一个完整的事件序列是由DOWN开始,UP结束,当ViewGroup要拦截事件的时候,那么后续的事件序列都交给他处理,而不用再调用onInterceptTouchEvent了.
1.onInterceptTouchEvent默认返回false,不进行拦截,所以想要拦截就要在自定义的ViewGroup重写这个方法
2.dispatchTouchEvent()使用for循环遍历子元素,这个for是倒序遍历的,即从最上层的view开始遍历,然后判断触摸点是否在子View的范围内或者子View是否在播放动画,均不符合遍历下一个子View,如果有子View,就调用子View的dispatchTouchEvent,如果ViewGroup没有子View,就调用super.dispatchTouchEvent
3.onTouchListener中的onTouch方法的优先级要高于onTouchEvent(),如果View调用了setOnclickListener和setOnLongClickListener,onTouchEvent就会返回true消耗这个事件;
总结一下:对于根ViewGroup,点击事件首先传递给他的dispatchTouchEvent(),如果该ViewGroup的onIterceptTouchEvent返回true,表示要拦截,就交给他的onTouchEvent进行处理,否则就交给子元素的dispatchTouchEvent进行处理,直到调用到最小的View的dispatchTouchEvent然后onTouchEvent进行处理,如果子View的onTouchEvent返回false,则传递给父View的onTouchEvent();
指的是measure,layout,draw
1.MeasureSpec是View的内部类,封装了一个View的规格尺寸,在Measure的流程中,系统会将View的LayoutParams根据父容器所施加的规则转换为对应的MeasureSpec,然后在onMeasure方法中根据MeasureSpec来确定View的宽和高;SpecMode有以下三种:
2.View的measure流程
对于一个直接继承自View的自定义View来说,mrap_content和match_content的属性的效果是一样的,因此想要实现自定义View的wrap_content,则要重写onMeasure方法,并对自定义View的wrap_content属性进行处理
如果View设置了背景,View的size取mMinWidth和mBackground.getMinimumWidth()的最大值,minWidth可以指定,默认为0
3.ViewGroup的measure流程
不仅要测量自身,还要测量子元素的measure()方法,在Viewgroup中没有onMeasure()方法,却定义了measureChildren()方法,根据父容器的MeasureSpec模式结合子元素和LayoutParams属性来得出子元素的MeasureSpec属性,
比如LinearLayout的measure的流程,如果是垂直方向则调用measureVertical方法,否则就调用measureHorizontal方法,这里分析一下垂直方向上的,如果是WRAP_CONTENT,就将每个子元素的高度和margin垂直高度相加,最后加上padding的值
4.View的layout流程
ViewGroup的layout用来确定子元素的位置,View的layout用来确定自身的位置,layout里面的四个参数l,t,r,b分别是左,上,右,下相对于父容器的距离
比如,如果是垂直方向上childTop值就是不断累加的,这样子元素就会一个一个垂直方向排列
5.View的draw流程
步骤1绘制背景调用了View 的drawBackground方法
步骤3绘制View的内容需要在自定义View重写onDraw()方法实现
步骤4ViewGroup调用dispatchDraw方法对子类View进行遍历,并调用子元素的draw方法,然后先判断是否有缓存,没有缓存就正常绘制,有就按缓存显示;
步骤6绘制装饰的方法View的onDrawFordground方法,用于绘制ScrollBar以及其他的装饰,并将它们绘制在视图内容的上层;
分为继承系统控件(TextView)和继承View两种
1.继承系统控件的自定义View
public class InvalidTextView extends AppCompatTextView {
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
public InvalidTextView(Context context) {
this(context, null);
}
public InvalidTextView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public InvalidTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
private void initView() {
mPaint.setColor(Color.RED);
mPaint.setStrokeWidth((float) 1.5);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = getWidth();
int height = getHeight();
canvas.drawLine(0, height / 2, width, height / 2, mPaint);
}
}
2.继承View的自定义View
public class RectView extends View {
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private int mColor;
public RectView(Context context) {
this(context, null);
}
public RectView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public RectView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//在这里对自定义属性进行设计
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RectView);
mColor = typedArray.getColor(R.styleable.RectView_rect_color, Color.GREEN);
typedArray.recycle();
initDraw();
}
private void initDraw() {
mPaint.setColor(mColor);
mPaint.setStrokeWidth((float) 1.5);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = getWidth();
int height = getHeight();
//对padding属性的修改
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
int paddingTop = getPaddingTop();
int paddingBottom = getPaddingBottom();
canvas.drawRect(0 + paddingLeft, 0 + paddingTop, width - paddingRight, height - paddingBottom, mPaint);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//对于wrap_content,需要自己设定一个默认值,如果不设置,wrap_content和match_parent是一样
//的效果,因为之前说过,直接继承与View的自定义View,这两个属性是一样的
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(600, 600);
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(600, heightSpecSize);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSpecSize, 600);
}
}
}
自定义属性:在values中创建attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="RectView">
<attr name="rect_color" format="color" />
</declare-styleable>
</resources>
3.自定义ViewGroup
实现了这种一个ViewPager,并在里面添加了两个ListView
public class HorizontalView extends ViewGroup {
private int lastInterceptX;
private int lastInterceptY;
private int lastX;
private int lastY;
//测量滑动速度
private VelocityTracker mTracker;
int currentIndex = 0;
int childWidth = 0;
private Scroller mScroller;
private static final String TAG = "HorizontalView";
public HorizontalView(Context context) {
super(context);
init();
}
public HorizontalView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public HorizontalView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
//scroller初始化
mScroller = new Scroller(getContext());
//测速器初始化
mTracker = VelocityTracker.obtain();
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercept = false;
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//这里设置为false,onTouchEvent就无法收到了
intercept = false;
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
break;
case MotionEvent.ACTION_MOVE:
int disX = x - lastInterceptX;
int disY = y - lastInterceptY;
//水平滑动时拦截,解决了滑动冲突
if (Math.abs(disX) > Math.abs(disY)) {
//开始进入onTouchEvent事件
intercept = true;
}
break;
case MotionEvent.ACTION_UP:
break;
}
//这里设置lastX是为了在onTouchEvent收不到时也能更新
lastX = x;
lastY = y;
lastInterceptX = x;
lastInterceptY = y;
return intercept;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//滑动过程中再次触摸时中断
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
break;
case MotionEvent.ACTION_MOVE:
int disX = x - lastX;
scrollBy(-disX, 0);
break;
case MotionEvent.ACTION_UP:
//getScrollX为当前的View距离初始左上角的距离
int distance = getScrollX() - childWidth * currentIndex;
if (Math.abs(distance) > childWidth / 2) {
if (distance > 0) {
currentIndex++;
} else {
currentIndex--;
}
} else {
mTracker.computeCurrentVelocity(1000);
float xVelocity = mTracker.getXVelocity();
if (Math.abs(xVelocity) > 50) {
if (xVelocity > 0) {
currentIndex--;
} else {
currentIndex++;
}
}
}
currentIndex = currentIndex < 0 ? 0 : currentIndex > getChildCount() - 1 ?
getChildCount() - 1 : currentIndex;
smoothScrollTo(currentIndex * childWidth, 0);
mTracker.clear();
break;
default:
break;
}
lastX = x;
lastY = y;
return true;
}
private void smoothScrollTo(int destX, int destY) {
//起点x,起点y,距离x,距离y
mScroller.startScroll(getScrollX(), getScrollY(), destX - getScrollX(), destY - getScrollY(), 1000);
//重绘
invalidate();
}
@Override
public void computeScroll() {
super.computeScroll();
//滑动是否还在继续
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
invalidate();
}
}
//确定子View的位置
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childCount = getChildCount();
int left = 0;
View child;
for (int i = 0; i < childCount; i++) {
child = getChildAt(i);
if (child.getVisibility() != View.GONE) {
int measuredWidth = child.getMeasuredWidth();
childWidth = measuredWidth;
child.layout(left, 0, left + measuredWidth, child.getMeasuredHeight());
//left要累加
left += measuredWidth;
}
}
}
//要对wrap_content特殊处理,所以需要重写onMeasure,绘制子View
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);
//没有子元素
if (getChildCount() == 0) {
setMeasuredDimension(0, 0);
//宽和高都为wrap_content
} else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
View childOne = getChildAt(0);
int measuredWidth = childOne.getMeasuredWidth();
int measuredHeight = childOne.getMeasuredHeight();
setMeasuredDimension(measuredWidth * getChildCount(), measuredHeight);
//只有宽是wrap_content
} else if (widthMode == MeasureSpec.AT_MOST) {
View childOne = getChildAt(0);
int measuredWidth = childOne.getMeasuredWidth();
setMeasuredDimension(measuredWidth * getChildCount(), heightSize);
//只有高是wrap_content
} else if (heightMode == MeasureSpec.AT_MOST) {
int measuredHeight = getChildAt(0).getMeasuredHeight();
setMeasuredDimension(widthSize, measuredHeight);
}
}
}
<com.example.a03view.HorizontalView
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/lv_one"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ListView
android:id="@+id/lv_two"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.example.a03view.HorizontalView>
String[] str1 = new String[]{"1","2","3","1","2","3","1","2","3","1","2","3"};
ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(
this,android.R.layout.simple_list_item_1,str1);
listView1.setAdapter(arrayAdapter);
String[] str2 = new String[]{"a","b","c"};
ArrayAdapter<String> arrayAdapter1 = new ArrayAdapter<String>(
this,android.R.layout.simple_expandable_list_item_1,str2);
listView2.setAdapter(arrayAdapter1);
本文地址:https://blog.csdn.net/qq873044564/article/details/107312399
如对本文有疑问, 点击进行留言回复!!
android -- ndk (stack corruption detected)
Android Span富文本图文混排 - ImageSpan(图文垂直居中)
Element DateTimePicker日期时间选择器的使用示例
【Appium踩坑】小米手机,启动报错:exited with code 255 writing to settings requires:android.permission.WRITE_SECUR
android 拍照 预览图与 照片分辨率(可视区域)不一致
[PAT顶级]1025 Keep at Most 100 Characters (35分)
网友评论