自定义ViewGroup/View(1)(Measure,Layout,Draw)

Android实现 刮刮乐效果- https://blog.csdn.net/huangliniqng/article/details/80722972
  Android刮刮乐效果的实现,自定义view,绘制出中奖信息,将一张图片绘制在中奖信息的上层,通过onTouchEvent监听用户手势,通过path记录绘制轨迹,设置绘制方式为DST_OUT,对view进行重绘,当达到一定阈值的时候,不在绘制图片只绘制中奖信息。

> View绘制流程
-- View体系的绘制流程是从ViewRootImpl的performTraversals方法开始的;
View的测量大小流程:performMeasure –> measure –> onMeasure等方法;
View的测量位置流程:performLayout –> layout –> onLayout等方法;

View的绘制流程:performDraw-> draw-> onDraw等方法;

在View的测量流程里,View的测量宽高是由父控件的MeasureSpec和View自身的LayoutParams共同决定的。

-- View的绘制:测量、布局、绘制流程;
private void performTraversals() {
        final View host = mView;
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
        performLayout(lp, mWidth, mHeight);
        performDraw();
    }

    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
                               int desiredWindowHeight) {
        final View host = mView;
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    }

    private void performDraw() {
        // draw(fullRedrawNeeded) --> drawSoftware
        mView.draw(canvas);
    }
 performTraversals方法巨长,这里只截取View绘制三大流程的起点。mView就是之前缓存的DecorView。之后便开始了View的measure、layout、draw、onMeasure、onLayout、ondraw。

自定义View涉及:View的层次结构,事件分发机制和View的工作原理等。

--   View 的测量、布局、绘制三大流程都是交由 ViewRootImpl 发起,而且还都是在 performTraversals() 方法中发起的,所以这个方法的逻辑很复杂,因为每次都需要根据相应状态判断是否需要三个流程都走,有时可能只需要执行 performDraw() 绘制流程,有时可能只执行 performMeasure() 测量和 performLayout() 布局流程(一般测量和布局流程是一起执行的)。不管哪个流程都会遍历一次 View 树,所以其实界面的绘制是需要遍历很多次的,如果页面层次太过复杂,每一帧需要刷新的 View 又很多时,耗时就会长一点。

-- View的绘制,从ViewRoot的performTraversals()方法开始依次调用perfromMeasure、performLayout和performDraw这三个方法。这三个方法分别完成顶级View的measure、layout和draw三大流程,其中perfromMeasure会调用measure,measure又会调用onMeasure,在onMeasure方法中则会对所有子元素进行measure,这个时候measure流程就从父容器传递到子元素中了,这样就完成了一次measure过程,接着子元素会重复父容器的measure,如此反复就完成了整个View树的遍历。同理,performLayout和performDraw也分别完成perfromMeasure类似的流程。通过这三大流程,分别遍历整棵View树,就实现了Measure,Layout,Draw这一过程,View就绘制出来了。

--View是一帧一帧绘制的,每一帧绘制都经历了measure->layout->draw这三个阶段,绘制完一帧之后,如果UI需要更新,比如用户滚动了ListView,那么又会绘制下一帧,再次经历measure->layout->draw方法。

Android应用层View绘制流程与源码分析- https://blog.csdn.net/yanbober/article/details/46128379
requestLayout()方法会调用measure过程和layout过程,不会调用draw过程,也不会重新绘制任何View包括该调用者本身。

https://i-blog.csdnimg.cn/blog_migrate/187e0a9f0fac83dfdf4cc4a9a8e70b64.png

> 自定义控件可能重写的方法:onMeasure(),onLayout(),onDraw()等

-- Android自定义控件的三种实现方式:https://blog.csdn.net/fictionss/article/details/78285167

自定义View文字居中api小研究- https://www.jianshu.com/p/6bcc06251550

 自定义控件分为自定义组合组件和自定义View。自定义View的实现方式大概可以分为三种,自绘控件、组合控件、以及继承控件。

-- setContentView()绘制流程:
Activity setContentView—>Window setContentView—>PhoneWindow setContentView—->PhoneWindow installDecor—–>PhoneWindow generateLayout——>PhoneWindow mLayoutInflater.inflate(layoutResID, mContentParent);

-- 任何一个View也是有生命周期的,从构造——>onFinishInflate()——>attachToWindow() ——>onMesure()——>onLayout()——>onDraw——>响应事件交互——>onDetachFromWindow()。

-- 自定义 View分类:
(1). 继承 View 重写 onDraw() 方法,用来实现一些不规则的视图,需要自己支持 wrap_content, 并且也需要自己处理 padding.
(2).继承 ViewGroup 派生特殊的 Layout,用于自定义布局,需要合适地处理 ViewGroup 的测量、布局的过程,并同时处理处理子元素的测量和布局过程。
(3). 继承特定的 View ,用户扩展某种已有的 View 的特性。
(4).继承特定的 ViewGroup (例如 LinearLayout)

-- 自定义 View注意事项:
(1).让 View 支持 wrap_content
(2).如有必要,让 View 支持 padding, 在 draw 方法中支持处理 padding.
(3).尽量不要在 View 中使用 Handler, 可使用其内部的 post 方法.
(4).View 中有线程或者动画时:在 onAttachToWindow 中启动线程和动画;在 onDetachFromWindow 方法中要停止线程和动画.
(5). View 带有滑动嵌套情况时,需要处理好滑动冲突。

> 监听手指上下左右滑动

  在Android应用中,经常需要手势滑动操作,比如上下滑动,或左右方向滑动,处理手势滑动通常有两种方法:一种是单独实现setOnTouchListener监听器来,另一种是构建手势探测器。
  第一种方法,就是在要实现滑动的View中,实现OnTouchListener监听事件,然后判断KeyDonw和KeyUp 直接的位置距离来判断滑动方向。第二种方法:就是构建手势探测器,如GestureDetector mygesture = new GestureDetector(this);,然后在onFling方法中根据MotionEvent的两个参数的 按下和滑动以及放开的位置和距离来判断滑动方向以及滑动速度等的。要构建GestureDetector,必须要和OnTouchListener一起使用,因为必须设置Touch监听。
Android开发之手势滑动(滑动手势监听)详解- http://www.cnblogs.com/JczmDeveloper/p/3772967.html
用Activity的onTouchEvent方法实现监听手指上下左右滑动- http://blog.csdn.net/qiantujava/article/details/9903891

-- 移动开发之手势与双指缩放-http://geek.csdn.net/news/detail/193169
  iOS的Safari浏览器是第一个支持多点触摸的浏览器,并提供触摸API供开发者使用,到后来Android 3也开始支持多点触摸,各大浏览器也借鉴Safari提出了触摸API,除了个别硬件支持属性外,大致相同,这是值得庆幸的。
  IE10支持多点触摸,但是其多点触摸与iOS和Android不同,iOS和Android浏览器为多点触摸提供一个包含touches数组的事件,包含所有多点触摸对象,而IE10为多点触摸的每一个触摸点创建一个单独的触摸事件。

-- 自定义控件

Android自定义控件- http://blog.csdn.net/q4878802/article/category/5664655

各种Android自定义控件、Widget、Material Design- http://www.ctolib.com/topics-113378.html
自定义控件其实很简单- http://blog.csdn.net/aigestudio?viewmode=contents

自定义控件三部曲之绘图- http://blog.csdn.net/harvic880925/article/category/1707319/1
自定义view- http://blog.csdn.net/wingichoy/article/category/6032967

安卓自定义View进阶-多点触控详解- http://www.gcssloop.com/customview/multi-touch

让你的app提升一个档次-Android酷炫自定义控件-- http://www.jianshu.com/p/356619fe64d5#

 -- Android 如何保存与恢复自定义View的状态?- http://blog.csdn.net/a553181867/article/details/54633151

Android基础——控件的混合生命周期- http://blog.csdn.net/woshimalingyi/article/details/60975884
View的工作原理- https://www.jianshu.com/p/3b3335223425

Android应用自定义View绘制方法手册:http://blog.csdn.net/yanbober/article/details/50577855
Android应用层View绘制流程与源码分析:http://blog.csdn.net/yanbober/article/details/46128379

深入理解android view 生命周期 -http://blog.csdn.net/sun_star1chen/article/details/44626433

     自定义View时我们不可避免的要和View生命周期相关函数打交道,可能需要重新其中的某个或某几个来满足定制的需求,因此了解View的生命周期是Android程序猿进阶的必经之路。

  View本身的大小,由onMeasure()决定;View在ViewGroup中的位置,由onLayout()决定;绘制View,onDraw定义了如何绘制这个View。

  自定义View的三个主要方法:onMeasure(),onLayout(),onDraw()

1、自定义View的属性

2、在View的构造方法中获得我们自定义的属性

【Android】View绘制过程分析之measure-- http://blog.csdn.net/rongxinhua/article/details/19649015

【Android】View绘制过程分析之layout-- http://blog.csdn.net/rongxinhua/article/details/19650373
【Android】View绘制过程分析之draw-- http://blog.csdn.net/rongxinhua/article/details/19676689

一、onMeasure()方法
onMeasure(int widthMeasureSpec,int heightMeasureSpec)
1、调用时间:当控件的父元素放置该控件时,用于告诉父元素该控件需要的大小。
2、传入参数:widthMeasureSpec,heightMeasureSpec。这两个传入参数由高32位和低16位组成,高32位保存的值叫specMode,可以通过MeasureSpec.getMode()获取;低16位为specSize可以由MeasureSpec.getSize()获取。这两个值是由ViewGroup中的layout_width,layout_height和padding以及View自身的layout_margin共同决定。权值weight也是尤其需要考虑的因素,有它的存在情况可能会稍微复杂点。

二、onLayout()方法
onLayout(boolean changed, int left, int top,int right,int bottom);
父容器的onLayout()调用子类的onLayout()来确定子view在viewGroup中的位置,如:onLayout(10,10,100,100)表示子容器在父容器中(10,10)位置显示,长、宽都是90。结合onMeasure()方法使用可以确定子view的布局。

三、onDraw()方法
onDraw(Canvas canvas)
自定义view的关键方法,用于绘制界面,可以重写此方法以绘制自定义View。

import java.util.HashSet;
import java.util.Random;
import java.util.Set;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;

import com.example.customview01.R;

public class CustomTitleView extends View
{
/**
* 文本
*/
private String mTitleText;
/**
* 文本的颜色
*/
private int mTitleTextColor;
/**
* 文本的大小
*/
private int mTitleTextSize;

/**
* 绘制时控制文本绘制的范围
*/
private Rect mBound;
private Paint mPaint;
public CustomTitleView(Context context, AttributeSet attrs)
{
this(context, attrs, 0);
}
public CustomTitleView(Context context)
{
this(context, null);
}
/**
* 获得我自定义的样式属性,构造方法

* @param context
* @param attrs
* @param defStyle
*/
public CustomTitleView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
/**
* 获得我们所定义的自定义样式属性
*/
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomTitleView, defStyle, 0);
int n = a.getIndexCount();
for (int i = 0; i < n; i++)
{
int attr = a.getIndex(i);
switch (attr)
{
case R.styleable.CustomTitleView_titleText:
mTitleText = a.getString(attr);
break;
case R.styleable.CustomTitleView_titleTextColor:
// 默认颜色设置为黑色
mTitleTextColor = a.getColor(attr, Color.BLACK);
break;
case R.styleable.CustomTitleView_titleTextSize:
// 默认设置为16sp,TypeValue也可以把sp转化为px
mTitleTextSize = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
break;

}

}
a.recycle();
/**
* 获得绘制文本的宽和高
*/
mPaint = new Paint();
mPaint.setTextSize(mTitleTextSize);
// mPaint.setColor(mTitleTextColor);
mBound = new Rect();
mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound);

this.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
mTitleText = randomText();
postInvalidate();
}
});
}
private String randomText()
{
Random random = new Random();
Set<Integer> set = new HashSet<Integer>();
while (set.size() < 4)
{
int randomInt = random.nextInt(10);
set.add(randomInt);
}
StringBuffer sb = new StringBuffer();
for (Integer i : set)
{
sb.append("" + i);
}
return sb.toString();
}


//设置控件的高度和宽度
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = 0;
int height = 0;
/**
* 设置宽度
*/
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
switch (specMode)
{
case MeasureSpec.EXACTLY:// 明确指定了
width = getPaddingLeft() + getPaddingRight() + specSize;
break;
case MeasureSpec.AT_MOST:// 一般为WARP_CONTENT
width = getPaddingLeft() + getPaddingRight() + mBound.width();
break;
}
/**
* 设置高度
*/
specMode = MeasureSpec.getMode(heightMeasureSpec);
specSize = MeasureSpec.getSize(heightMeasureSpec);
switch (specMode)
{
case MeasureSpec.EXACTLY:// 明确指定了
height = getPaddingTop() + getPaddingBottom() + specSize;
break;
case MeasureSpec.AT_MOST:// 一般为WARP_CONTENT
height = getPaddingTop() + getPaddingBottom() + mBound.height();
break;
}
setMeasuredDimension(width, height);


}
//
@Override
protected void onDraw(Canvas canvas)
{
mPaint.setColor(Color.YELLOW);
canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);


mPaint.setColor(mTitleTextColor);
canvas.drawText(mTitleText, getWidth() / 2 - mBound.width() / 2, getHeight() / 2 + mBound.height() / 2, mPaint);
}
}

转载地址:http://blog.csdn.net/lmj623565791/article/details/24252901

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值