声明:本文中使用的Demo的Git地址:https://github.com/NoClay/TestView.git
1.自定义View中需要知道的几个类
1.MeasureSpec
public static class MeasureSpec { private static final int MODE_SHIFT = 30; private static final int MODE_MASK = 0x3 << MODE_SHIFT; //父容器不对View有任何限制,要多大给多大,一般用于系统内部 public static final int UNSPECIFIED = 0 << MODE_SHIFT; //父容器已经检测出View所需要的精确大小,这个时候view的最终大小就是指定的SpecSize的值 //对应于使用match_parent和具体的数值如37dp这种 public static final int EXACTLY = 1 << MODE_SHIFT; //父容器指定了一个可用大小的SpecSize,View的大小不能大于这个值,相当于wrap_content public static final int AT_MOST = 2 << MODE_SHIFT; //获取一个SpecSize的模式 @MeasureSpecMode public static int getMode(int measureSpec) ; //获取一个SpecSize的大小 public static int getSize(int measureSpec) ; //对一个SpecSize的大小进行调整 static int adjust(int measureSpec, int delta); }
2.Paint
/** * Paint类介绍 * * Paint即画笔,在绘图过程中起到了极其重要的作用,画笔主要保存了颜色, * 样式等绘制信息,指定了如何绘制文本和图形,画笔对象有很多设置方法, * 大体上可以分为两类,一类与图形绘制相关,一类与文本绘制相关。 * * 1.图形绘制 * setARGB(int a,int r,int g,int b); * 设置绘制的颜色,a代表透明度,r,g,b代表颜色值。 * * setAlpha(int a); * 设置绘制图形的透明度。 * * setColor(int color); * 设置绘制的颜色,使用颜色值来表示,该颜色值包括透明度和RGB颜色。 * * setAntiAlias(boolean aa); * 设置是否使用抗锯齿功能,会消耗较大资源,绘制图形速度会变慢。 * * setDither(boolean dither); * 设定是否使用图像抖动处理,会使绘制出来的图片颜色更加平滑和饱满,图像更加清晰 * * setFilterBitmap(boolean filter); * 如果该项设置为true,则图像在动画进行中会滤掉对Bitmap图像的优化操作,加快显示 * 速度,本设置项依赖于dither和xfermode的设置 * * setMaskFilter(MaskFilter maskfilter); * 设置MaskFilter,可以用不同的MaskFilter实现滤镜的效果,如滤化,立体等 * * setColorFilter(ColorFilter colorfilter); * 设置颜色过滤器,可以在绘制颜色时实现不用颜色的变换效果 * * setPathEffect(PathEffect effect); * 设置绘制路径的效果,如点画线等 * * setShader(Shader shader); * 设置图像效果,使用Shader可以绘制出各种渐变效果 * * setShadowLayer(float radius ,float dx,float dy,int color); * 在图形下面设置阴影层,产生阴影效果,radius为阴影的角度,dx和dy为阴影在x轴和y轴上的距离,color为阴影的颜色 * * setStyle(Paint.Style style); * 设置画笔的样式,为FILL,FILL_OR_STROKE,或STROKE * * setStrokeCap(Paint.Cap cap); * 当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的图形样式,如圆形样式 * Cap.ROUND,或方形样式Cap.SQUARE * * setSrokeJoin(Paint.Join join); * 设置绘制时各图形的结合方式,如平滑效果等 * * setStrokeWidth(float width); * 当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的粗细度 * * setXfermode(Xfermode xfermode); * 设置图形重叠时的处理方式,如合并,取交集或并集,经常用来制作橡皮的擦除效果 * * 2.文本绘制 * setFakeBoldText(boolean fakeBoldText); * 模拟实现粗体文字,设置在小字体上效果会非常差 * * setSubpixelText(boolean subpixelText); * 设置该项为true,将有助于文本在LCD屏幕上的显示效果 * * setTextAlign(Paint.Align align); * 设置绘制文字的对齐方向 * * setTextScaleX(float scaleX); * 设置绘制文字x轴的缩放比例,可以实现文字的拉伸的效果 * * setTextSize(float textSize); * 设置绘制文字的字号大小 * * setTextSkewX(float skewX); * 设置斜体文字,skewX为倾斜弧度 * * setTypeface(Typeface typeface); * 设置Typeface对象,即字体风格,包括粗体,斜体以及衬线体,非衬线体等 * * setUnderlineText(boolean underlineText); * 设置带有下划线的文字效果 * * setStrikeThruText(boolean strikeThruText); * 设置带有删除线的效果 * */
3.Canvas
public class Canvas{ 弧线(arcs)、填充颜色(argb和color)、 Bitmap、圆(circle和oval)、点(point)、 线(line)、矩形(Rect)、图片(Picture)、 圆角矩形 (RoundRect)、文本(text)、 顶点(Vertices)、路径(path)。 Canvas位置转换的方法: rorate(旋转)、 scale(缩放)、 translate(变换)、 skew(扭曲)等 }
2.View绘制的三大流程
1.View绘制的三大流程--measure
主要作用:为整个View树计算实际的大小,即设置实际的高(对应属性:mMeasuredHeight)和宽(对应属性:
mMeasureWidth),每个View的控件的实际宽高都是由父视图和本身视图决定的。
具体的调用链如下:
ViewRoot根对象地属性mView(其类型一般为ViewGroup类型)调用measure()方法去计算View树的大小,回调View/ViewGroup对象的onMeasure()方法,该方法实现的功能如下:
1、设置本View视图的最终大小,该功能的实现通过调用setMeasuredDimension()方法去设置实际的高(对应属性:mMeasuredHeight)和宽(对应属性:mMeasureWidth) ;
2 、如果该View对象是个ViewGroup类型,需要重写该onMeasure()方法,对其子视图进行遍历的measure()过程。对每个子视图的measure()过程,是通过调用父类ViewGroup.java类里的measureChildWithMargins()方法去实现,该方法内部只是简单地调用了View对象的measure()方法。(由于measureChildWithMargins()方法只是一个过渡层更简单的做法是直接调用View对象的measure()方法)。
整个measure调用流程就是个树形的递归过程
看流程图如下,如果View树申请重新测量,则进行重新测量,从顶层View依次向下递归测量整个View树
public final void measure(int widthMeasureSpec, int heightMeasureSpec) { //.... //回调onMeasure()方法 onMeasure(widthMeasureSpec, heightMeasureSpec); //more } //回调View视图里的onMeasure过程 private void onMeasure(int height , int width){ //设置该view的实际宽(mMeasuredWidth)高(mMeasuredHeight) //1、该方法必须在onMeasure调用,否者报异常。 setMeasuredDimension(h , l) ; //2、如果该View是ViewGroup类型,则对它的每个子View进行measure()过程 int childCount = getChildCount() ; for(int i=0 ;i<childCount ;i++){ //2.1、获得每个子View对象引用 View child = getChildAt(i) ; //整个measure()过程就是个递归过程 //该方法只是一个过滤器,最后会调用measure()过程 ;或者 measureChild(child , h, i)方法都 measureChildWithMargins(child , h, i) ; //其实,对于我们自己写的应用来说,最好的办法是去掉框架里的该方法,直接调用view.measure(),如下: //child.measure(h, l) } } //该方法具体实现在ViewGroup.java里 。 protected void measureChildWithMargins(View v, int height , int width){ v.measure(h,l) }
2.View绘制的三大流程--layout
主要作用 :为将整个根据子视图的大小以及布局参数将View树放到合适的位置上。
具体的调用链如下:
host.layout()开始View树的布局,继而回调给View/ViewGroup类中的layout()方法。具体流程如下
1 、layout方法会设置该View视图位于父视图的坐标轴,即mLeft,mTop,mLeft,mBottom(调用setFrame()函数去实现)接下来回调onLayout()方法(如果该View是ViewGroup对象,需要实现该方法,对每个子视图进行布局) ;
2、如果该View是个ViewGroup类型,需要遍历每个子视图chiildView,调用该子视图的layout()方法去设置它的坐标值。
/* final 标识符 , 不能被重载 , 参数为每个视图位于父视图的坐标轴 * @param l Left position, relative to parent * @param t Top position, relative to parent * @param r Right position, relative to parent * @param b Bottom position, relative to parent */ public final void layout(int l, int t, int r, int b) { boolean changed = setFrame(l, t, r, b); //设置每个视图位于父视图的坐标轴 if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) { if (ViewDebug.TRACE_HIERARCHY) { ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT); } onLayout(changed, l, t, r, b);//回调onLayout函数 ,设置每个子视图的布局 mPrivateFlags &= ~LAYOUT_REQUIRED; } mPrivateFlags &= ~FORCE_LAYOUT; } // layout()过程 ViewRoot.java // 发起layout()的"发号者"在ViewRoot.java里的performTraversals()方法, mView.layout() private void performTraversals(){ //... View mView ; mView.layout(left,top,right,bottom) ; //.... } //回调View视图里的onLayout过程 ,该方法只由ViewGroup类型实现 private void onLayout(int left , int top , right , bottom){ //如果该View不是ViewGroup类型 //调用setFrame()方法设置该控件的在父视图上的坐标轴 setFrame(l ,t , r ,b) ; //-------------------------- //如果该View是ViewGroup类型,则对它的每个子View进行layout()过程 int childCount = getChildCount() ; for(int i=0 ;i<childCount ;i++){ //2.1、获得每个子View对象引用 View child = getChildAt(i) ; //整个layout()过程就是个递归过程 child.layout(l, t, r, b) ; } }
3.View绘制的三大流程--draw
由ViewRoot对象的performTraversals()方法调用draw()方法发起绘制该View树,值得注意的是每次发起绘图时,并不会重新绘制每个View树的视图,而只会重新绘制那些“需要重绘”的视图,View类内部变量包含了一个标志位DRAWN,当该视图需要重绘时,就会为该View添加该标志位。
调用流程 :
mView.draw()开始绘制,draw()方法实现的功能如下:
1 、绘制该View的背景
2 、为显示渐变框做一些准备操作(见5,大多数情况下,不需要改渐变框)
3、调用onDraw()方法绘制视图本身 (每个View都需要重载该方法,ViewGroup不需要实现该方法)
4、调用dispatchDraw ()方法绘制子视图(如果该View类型不为ViewGroup,即不包含子视图,不需要重载该方法)值得说明的是,ViewGroup类已经为我们重写了dispatchDraw ()的功能实现,应用程序一般不需要重写该方法,但可以重载父类函数实现具体的功能。
dispatchDraw()方法内部会遍历每个子视图,调用drawChild()去重新回调每个子视图的draw()方法(注意,这个 地方“需要重绘”的视图才会调用draw()方法)。值得说明的是,ViewGroup类已经为我们重写了dispatchDraw()的功能实现,应用程序一般不需要重写该方法,但可以重载父类函数实现具体的功能。
// draw()过程 ViewRoot.java // 发起draw()的"发号者"在ViewRoot.java里的performTraversals()方法, 该方法会继续调用draw()方法开始绘图 private void draw(){ //... View mView ; mView.draw(canvas) ; //.... } //回调View视图里的onLayout过程 ,该方法只由ViewGroup类型实现 private void draw(Canvas canvas){ //该方法会做如下事情 //1 、绘制该View的背景 //2、为绘制渐变框做一些准备操作 //3、调用onDraw()方法绘制视图本身 //4、调用dispatchDraw()方法绘制每个子视图,dispatchDraw()已经在Android框架中实现了,在ViewGroup方法中。 // 应用程序程序一般不需要重写该方法,但可以捕获该方法的发生,做一些特别的事情。 //5、绘制渐变框 } //ViewGroup.java中的dispatchDraw()方法,应用程序一般不需要重写该方法 @Override protected void dispatchDraw(Canvas canvas) { // //其实现方法类似如下: int childCount = getChildCount() ; for(int i=0 ;i<childCount ;i++){ View child = getChildAt(i) ; //调用drawChild完成 drawChild(child,canvas) ; } } //ViewGroup.java中的dispatchDraw()方法,应用程序一般不需要重写该方法 protected void drawChild(View child,Canvas canvas) { // .... //简单的回调View对象的draw()方法,递归就这么产生了。 child.draw(canvas) ; //......... }
3.自定义View的一些重要方法
1.invalidate()方法 :
说明:请求重绘View树,即draw()过程,假如视图发生大小没有变化就不会调用layout()过程,并且只绘制那些“需要重绘的”视图,即谁(View的话,只绘制该View ;ViewGroup,则绘制整个ViewGroup)请求invalidate()方法,就绘制该视图。
一般引起invalidate()操作的函数如下:
1、直接调用invalidate()方法,请求重新draw(),但只会绘制调用者本身。
2、setSelection()方法:请求重新draw(),但只会绘制调用者本身。
3、setVisibility()方法 :当View可视状态在INVISIBLE转换VISIBLE时,会间接调用invalidate()方法,
继而绘制该View。
4 、setEnabled()方法 :请求重新draw(),但不会重新绘制任何视图包括该调用者本身。
2.requestLayout()方法 :会导致调用measure()过程 和 layout()过程 。
说明:只是对View树重新布局layout过程包括measure()和layout()过程,不会调用draw()过程,但不会重新绘制任何视图包括该调用者本身。
一般引起操作的方法如下:
setVisibility()方法:
当View的可视状态在INVISIBLE/VISIBLE 转换为GONE状态时,会间接调用requestLayout() 和invalidate方法。 同时,由于整个个View树大小发生了变化,会请求measure()过程以及draw()过程,同样地,只绘制需要“重新绘制”的视图。
3.requestFocus()函数说明:
说明:请求View树的draw()过程,但只绘制“需要重绘”的视图。
4.实例
1.继承自View重写onDraw
public class HeartWavesView extends View { private int mTableLineColor = Color.RED; private int mWavesLineColor = Color.BLACK; private int mTitleColor = Color.BLACK; private int mTitleSize = 30; private int mXYTextSize = 20; private Context context; private static final String TAG = "HeartWavesView"; private Paint paintWavesLine; private Paint paintTableLine; private TextPaint paintTitle; private TextPaint paintXYText; private boolean isFirstDrawPoint = true; private boolean isFirstDrawBackground = true; private int height; private int width; private int leftPadding; private int rightPadding; private int topPadding; private int bottomPadding; private int maxY = 2100; private int minY = -2100; private int maxX = 120; private int x_num = 25; private int y_num; private int grid_width; //x轴每个小格子对应的秒 //y轴每个小格子对应的指数 private int grid_second = 5; private float grid_num; private int zeroCurY; private int yStartNum; private int workWidth; private int workHeight; //几秒钟一次数据,默认为1秒1次 private float dataHz = 1; private List<PointXY> pointList; private String title = "心电图"; public HeartWavesView(Context context) { super(context); this.context = context; initView(); } public HeartWavesView(Context context, AttributeSet attrs) { super(context, attrs); this.context = context; resolveAttrs(attrs); initView(); } private void resolveAttrs(AttributeSet attrs) { TypedArray typeArray = context.obtainStyledAttributes(attrs, R.styleable.HeartWavesView); mTableLineColor = typeArray.getColor(R.styleable.HeartWavesView_tableLineColor, Color.RED); mTitleColor = typeArray.getColor(R.styleable.HeartWavesView_titleColor, Color.BLACK); mWavesLineColor = typeArray.getColor(R.styleable.HeartWavesView_wavesLineColor, Color.BLACK); mTitleSize = typeArray.getDimensionPixelSize(R.styleable.HeartWavesView_titleSize, 30); mXYTextSize = typeArray.getDimensionPixelSize(R.styleable.HeartWavesView_xyTextSize, 20); typeArray.recycle(); } public HeartWavesView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.context = context; resolveAttrs(attrs); initView(); } private void initView() { //生成抗锯齿的画笔 pointList = new ArrayList<>(); paintWavesLine = new Paint(Paint.ANTI_ALIAS_FLAG); //设置画笔粗细 paintWavesLine.setStrokeWidth(2.5f); //设置画笔颜色 paintWavesLine.setColor(mWavesLineColor); paintTableLine = new Paint(); paintTableLine.setColor(mTableLineColor); paintTableLine.setAntiAlias(true); paintWavesLine.setStrokeWidth(4); paintTitle = new TextPaint(); paintTitle.setTextSize(mTitleSize); paintTitle.setColor(mTitleColor); paintXYText = new TextPaint(); paintXYText.setColor(mTitleColor); paintXYText.setTextSize(mXYTextSize); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (isFirstDrawBackground) { height = getHeight(); width = getWidth(); leftPadding = rightPadding = bottomPadding = topPadding = 100; } drawBackground(canvas); drawWaves(canvas); } private void drawWaves(Canvas canvas) { PointXY start = new PointXY(); PointXY end = new PointXY(); for (int i = 0; i < pointList.size() - 1; i++) { start = pointList.get(i); end = pointList.get(i + 1); canvas.drawLine(start.getX(), start.getY(), end.getX(), end.getY(), paintWavesLine); } } private void drawBackground(Canvas canvas) { if (isFirstDrawBackground) { isFirstDrawBackground = false; x_num = maxX / grid_second; x_num = x_num % 5 == 0 ? x_num : (x_num % 5 > 3 ? (x_num / 5 + 1) * 5 : x_num / 5 * 5); grid_width = (width - leftPadding - rightPadding) / x_num; y_num = (height - topPadding - rightPadding) / grid_width; y_num = y_num % 5 == 0 ? y_num : (y_num % 5 > 3 ? (y_num / 5 + 1) * 5 : y_num / 5 * 5); //获取工作区的宽和高 workWidth = grid_width * x_num; workHeight = grid_width * y_num; //获取xy轴比例尺 //获得y轴0标识位的位置 if (maxY > 0 && minY >= 0) { yStartNum = maxY; grid_num = maxY / y_num; zeroCurY = y_num; } else if (maxY <= 0 && minY < 0) { yStartNum = 0; grid_num = -minY / y_num; zeroCurY = 0; } else { zeroCurY = y_num / 2; zeroCurY = zeroCurY % 5 == 0 ? zeroCurY : (zeroCurY % 5 > 3) ? (zeroCurY / 5 + 1) * 5 : (zeroCurY / 5 * 5); grid_num = Math.max(maxY, minY) / Math.min(y_num - zeroCurY, zeroCurY); yStartNum = (int) (zeroCurY * grid_num); } } for (int i = 0; i <= x_num; i++) { paintTableLine.setStrokeWidth(1f); if (i % 5 == 0) { paintTableLine.setStrokeWidth(3f); String label = grid_second * i + ""; canvas.drawText(label, leftPadding + i * grid_width - mXYTextSize / 2, workHeight + bottomPadding / 2 + topPadding, paintXYText); } canvas.drawLine(leftPadding + i * grid_width, topPadding, leftPadding + i * grid_width, topPadding + workHeight, paintTableLine); } for (int i = 0; i <= y_num; i++) { paintTableLine.setStrokeWidth(1f); if (i % 5 == 0) { paintTableLine.setStrokeWidth(3f); String label = yStartNum - i * grid_num + ""; canvas.drawText(label, leftPadding / 5, topPadding + i * grid_width, paintXYText); } canvas.drawLine(leftPadding, topPadding + i * grid_width, leftPadding + workWidth, topPadding + i * grid_width, paintTableLine); } canvas.drawText(title, width / 2 - mTitleSize * title.length() / 2, topPadding / 2, paintTitle); } public void drawNextPoint(float y) { if (!isFirstDrawBackground) { if (isFirstDrawPoint) { isFirstDrawPoint = false; PointXY point = new PointXY(); point.setX(leftPadding); point.setY(zeroCurY * grid_width + topPadding); pointList.add(point); } PointXY lastPoint = pointList.get(pointList.size() - 1); Log.d(TAG, "drawNextPoint: size" + pointList.size()); if (pointList.size() == maxX- 1) { pointList.clear(); lastPoint.setX(leftPadding); } PointXY nowPoint = new PointXY(); nowPoint.setX(dataHz / grid_second * grid_width + lastPoint.getX()); nowPoint.setY((yStartNum - y) / grid_num * grid_width + topPadding); pointList.add(nowPoint); invalidate(); } } public Paint getPaintWavesLine() { return paintWavesLine; } public void setPaintWavesLine(Paint paintWavesLine) { this.paintWavesLine = paintWavesLine; } public Paint getPaintTableLine() { return paintTableLine; } public void setPaintTableLine(Paint paintTableLine) { this.paintTableLine = paintTableLine; } public TextPaint getPaintTitle() { return paintTitle; } public void setPaintTitle(TextPaint paintTitle) { this.paintTitle = paintTitle; } public TextPaint getPaintXYText() { return paintXYText; } public void setPaintXYText(TextPaint paintXYText) { this.paintXYText = paintXYText; } public int getLeftPadding() { return leftPadding; } public void setLeftPadding(int leftPadding) { this.leftPadding = leftPadding; } public int getRightPadding() { return rightPadding; } public void setRightPadding(int rightPadding) { this.rightPadding = rightPadding; } public int getTopPadding() { return topPadding; } public void setTopPadding(int topPadding) { this.topPadding = topPadding; } public int getBottomPadding() { return bottomPadding; } public void setBottomPadding(int bottomPadding) { this.bottomPadding = bottomPadding; } public int getMaxY() { return maxY; } public void setMaxY(int maxY) { this.maxY = maxY; } public int getMinY() { return minY; } public void setMinY(int minY) { this.minY = minY; } public int getMaxX() { return maxX; } public void setMaxX(int maxX) { this.maxX = maxX; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public class PointXY { private float x; private float y; public float getX() { return x; } public void setX(float x) { this.x = x; } public float getY() { return y; } public void setY(float y) { this.y = y; } } }
2.继承自ViewGroup实现特定的layout
public class HorizontalScrollViewEx extends ViewGroup { private static final String TAG = "HorizontalScrollViewEx"; private int mChildrenSize; private int mChildWidth; private int mChildIndex; //记录上次滑动的坐标 private int mLastX = 0; private int mLastY = 0; //记录上次滑动的坐标(onInterceptTouchEvent) private int mLastXIntercept = 0; private int mLastYIntercept = 0; private Scroller mScroller; private VelocityTracker mVelocityTracker; public HorizontalScrollViewEx(Context context) { super(context); init(); } public HorizontalScrollViewEx(Context context, AttributeSet attrs) { super(context, attrs); init(); } public HorizontalScrollViewEx(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { if (mScroller == null) { mScroller = new Scroller(getContext()); mVelocityTracker = VelocityTracker.obtain(); } } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { boolean intercepted = false; int x = (int) ev.getX(); int y = (int) ev.getY(); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: { intercepted = false; if (!mScroller.isFinished()) { mScroller.abortAnimation(); //动画滚动到最终位置结束动画,这里代表本身HorizontalScrollViewEx处理触摸事件 intercepted = true; } break; } case MotionEvent.ACTION_MOVE: { int deltaX = x - mLastXIntercept; int deltaY = y - mLastYIntercept; if (Math.abs(deltaX) > Math.abs(deltaY)) { //水平滑动则处理此事件 intercepted = true; } else { intercepted = false; } break; } case MotionEvent.ACTION_UP: { intercepted = false; break; } default: break; } Log.d(TAG, "onInterceptTouchEvent: intercepted = " + intercepted); mLastX = x; mLastY = y; mLastXIntercept = x; mLastYIntercept = y; return intercepted; } /** * 本身在处理触摸事件 * @param event * @return */ @Override public boolean onTouchEvent(MotionEvent event) { mVelocityTracker.addMovement(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 deltaX = x - mLastX; int deltaY = y - mLastY; scrollBy(-deltaX, 0); break; } case MotionEvent.ACTION_UP:{ int scrollX = getScrollX(); mVelocityTracker.computeCurrentVelocity(1000); float xVelocity = mVelocityTracker.getXVelocity(); if(Math.abs(xVelocity) >= 50){ mChildIndex = xVelocity > 0 ? mChildIndex - 1 : mChildIndex + 1; }else{ mChildIndex = (scrollX + mChildWidth / 2) / mChildWidth; } mChildIndex = Math.max(0, Math.min(mChildIndex, mChildrenSize - 1)); int dx = mChildIndex * mChildWidth - scrollX; smoothScrollBy(dx, 0); mVelocityTracker.clear(); break; } default:break; } mLastX = x; mLastY = y; return true; } /** * 重写onMeasure方法,对容器内的测量进行修正 * @param widthMeasureSpec * @param heightMeasureSpec */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { /** * super.onMeasure方法内部调用了setMeasuredDimension(getDefaultSize( * getSuggestedMinimumWidth(), widthMeasureSpec), * getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); */ super.onMeasure(widthMeasureSpec, heightMeasureSpec); int measuredWidth = 0; int measuredHeight = 0; final int childCount = getChildCount(); measureChildren(widthMeasureSpec, heightMeasureSpec); int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec); int widthSpaceMode = MeasureSpec.getMode(widthMeasureSpec); int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec); int heightSpaceMode = MeasureSpec.getMode(heightMeasureSpec); if(childCount == 0){ //no child -- no width, no height setMeasuredDimension(0, 0); }else if(widthSpaceMode == MeasureSpec.AT_MOST && heightSpaceMode == MeasureSpec.AT_MOST){ //width is wrap and height is wrap final View childView = getChildAt(0); measuredWidth = childView.getMeasuredWidth() * childCount; measuredHeight = childView.getMeasuredHeight(); setMeasuredDimension(measuredWidth, measuredHeight); }else if(heightSpaceMode == MeasureSpec.AT_MOST){ //height is wrap but width not final View childView = getChildAt(0); measuredHeight = childView.getMeasuredHeight(); setMeasuredDimension(widthSpaceSize, measuredHeight); }else if(widthMeasureSpec == MeasureSpec.AT_MOST){ //width is wrap but height not final View childView = getChildAt(0); measuredWidth = childView.getMeasuredWidth(); setMeasuredDimension(measuredWidth, heightSpaceSize); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int childLeft = 0; final int childCount = getChildCount(); mChildrenSize = childCount; for(int i = 0; i < childCount; i ++){ final View childView = getChildAt(i); if(childView.getVisibility() != View.GONE){ final int childWidth = childView.getMeasuredWidth(); mChildWidth = childWidth; childView.layout(childLeft, 0, childLeft + childWidth, childView.getMeasuredHeight()); childLeft += childWidth; } } } /** * 两个startScrollBy方法,一个加上了时间间隔,一个并没有 * 之前整错了,用错了方法 * @param dx * @param dy */ private void smoothScrollBy(int dx, int dy) { mScroller.startScroll(getScrollX(), 0, dx, 0, 500); invalidate(); } @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); postInvalidate(); } } @Override protected void onDetachedFromWindow() { mVelocityTracker.recycle(); super.onDetachedFromWindow(); } }
3.继承自特定的ViewGroup
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_gravity="center" android:layout_height="match_parent" android:orientation="vertical" android:padding="3dp"> <ImageView android:id="@+id/image_view_image" android:layout_gravity="center_horizontal" android:src="@drawable/image" android:layout_width="match_parent" android:layout_height="50dp" android:padding="5dp"/> <TextView android:textSize="10sp" android:text="测试" android:id="@+id/image_view_title" android:layout_width="match_parent" android:layout_height="10dp" android:gravity="center_horizontal" /> </LinearLayout>
实现代码:
package noclay.testview; import android.content.Context; import android.content.res.TypedArray; import android.util.AttributeSet; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; /** * Created by 82661 on 2016/10/18. */ public class MyImageView extends LinearLayout { private static final String TAG = "MyImageView"; private TextView textView; private ImageView imageView; private LinearLayout linearLayout; private static Context context; public MyImageView(Context context) { super(context); this.context = context; init(); } public MyImageView(Context context, AttributeSet attrs) { super(context, attrs); this.context = context; init(); TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MyImageView); textView.setText(array.getText(R.styleable.MyImageView_title)); imageView.setImageDrawable(getResources(). getDrawable(array.getResourceId(R.styleable.MyImageView_image, R.drawable.image))); array.recycle(); } public MyImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.context = context; init(); TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MyImageView); textView.setText(array.getText(R.styleable.MyImageView_title)); imageView.setImageDrawable(getResources(). getDrawable(array.getResourceId(R.styleable.MyImageView_image, R.drawable.image))); array.recycle(); } public void init(){ LayoutInflater.from(getContext()).inflate(R.layout.my_iamge_view, this, true); textView = (TextView) findViewById(R.id.image_view_title); imageView = (ImageView) findViewById(R.id.image_view_image); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int measuredWidth = 0; int measuredHeight = 0; final int childCount = getChildCount(); measureChildren(widthMeasureSpec, heightMeasureSpec); int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec); int widthSpaceMode = MeasureSpec.getMode(widthMeasureSpec); int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec); int heightSpaceMode = MeasureSpec.getMode(heightMeasureSpec); Log.d(TAG, "onMeasure: width = " + widthSpaceSize); Log.d(TAG, "onMeasure: height = " + heightSpaceSize); if(widthSpaceMode == MeasureSpec.EXACTLY && heightSpaceMode == MeasureSpec.EXACTLY){ //在有具体数值的时候调整子项的大小 if(7 * widthSpaceSize <= 5 * heightSpaceSize){//规定长宽比例为5:7 setMeasure(widthSpaceSize); }else{ setMeasure(heightSpaceSize); } } } private void setMeasure(int value){ Log.d(TAG, "onMeasure: true"); LayoutParams lp = (LayoutParams) imageView.getLayoutParams(); lp.width = value * 4 / 5 ; lp.height = value * 4 / 5; lp.bottomMargin = lp.topMargin = value * 1 / 10; imageView.setLayoutParams(lp); lp = (LayoutParams) textView.getLayoutParams(); lp.width = value; lp.height = value * 2 / 5; textView.setLayoutParams(lp); textView.setTextSize(px2sp(lp.height / 2)); } public static int px2sp(float value) { final float scale =context.getResources().getDisplayMetrics().scaledDensity; return (int) (value / scale + 0.5f); } }
4.继承自特定的View
package noclay.testview; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.widget.ImageView; /** * Created by 82661 on 2016/10/25. */ public class MyCircleImageView extends ImageView { private Paint paint; public MyCircleImageView(Context context) { this(context, null); } public MyCircleImageView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MyCircleImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); paint = new Paint(); } /** * 绘制圆形图片 * * @author caizhiming */ @Override protected void onDraw(Canvas canvas) { Drawable drawable = getDrawable(); if (drawable != null) { Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap(); Bitmap b = getCircleBitmap(bitmap); final Rect rectSrc = new Rect(0, 0, b.getWidth(), b.getHeight()); final Rect rectDest = new Rect(0, 0, getWidth(), getHeight()); paint.reset(); canvas.drawBitmap(b, rectSrc, rectDest, paint); } else { super.onDraw(canvas); } } /** * 获取圆形图片方法 * * @param bitmap * @return Bitmap * @author caizhiming */ private Bitmap getCircleBitmap(Bitmap bitmap) { Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(output); final int color = 0xff424242; final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); paint.setAntiAlias(true); //设置背景色 canvas.drawARGB(0, 0, 0, 0); paint.setColor(color); int x = bitmap.getWidth(); canvas.drawCircle(x / 2, x / 2, x / 2, paint); //设置遮罩 paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); canvas.drawBitmap(bitmap, rect, rect, paint); return output; } }