文章目录

  • 1. View工作原理了解
    • 1.1 View测量measure
    • 1.2. View布局layout
    • 1.3. View绘制流程
      • 1.3.1 绘制例子
      • 1.3.2 动画的原理简单了解
  • 2. 自定义控件了解
    • 2.1 自定义属性
    • 2.2 组合控件
    • 2.3 属性修改
    • 2.4 注意事项
  • 3. 参考资料

文章整理中… …

1. View工作原理了解

onAttachedToWindow
onMeasure
onSizeChanged
onLayout
onDraw

从Android 的 FrameLayout 看 测量,布局,绘制 的自定义view的世界吧.
整个工作流程的核心 ViewRootImpl(ViewRoot) 的 performTraversals 开始的,
分别是 performMeasure->measure,performLayout->layout,performDraw->draw.

// ViewRootImpl.java
private void performTraversals() {... ...if (!mStopped || mReportNextDraw) {... ...int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); // 测量measure的关键函数    ... ...} ... ...if (didLayout) {performLayout(lp, desiredWindowWidth, desiredWindowHeight); // 布局layout的关键函数...}... ...if (!cancelDraw && !newSurface) {if (!skipDraw || mReportNextDraw) {... ...performDraw(); // 绘制 draw 的关键函数}}
}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(... ...)
}private void performDraw() {... ...draw(fullRedrawNeeded)->drawSoftware->mView.draw... ...
}

mView 是根视图 DecorView(继承关系)->FrameLayout->ViewGroup->View,看看下图的布局关系(转自网上的图片)

// 获取上层的视图view 的相关代码,了解下.
getWindow().getDecorView()
findViewById(android.R.id.content)

来看看整体的performTraversals调用过程 的 流程图

1.1 View测量measure

measure 流程了解下:
ViewRootImpl.performTraversals,performTraversals函数里面执行了 performMeasure

// ViewRootImpl.java
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {// mView 是 DecorView(View树的根节点是DecorView), 调用 measuremView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

FrameLayout 的 onMeasure 中 调用measure相关函数的,我们先看看

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// measureMatchParentChildren,false 表示 width 与 height 同时设置了 match_parent 或者指定了大小, true 反之.final boolean measureMatchParentChildren =MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;... ...for (int i = 0; i < count; i++) {final View child = getChildAt(i);if (mMeasureAllChildren || child.getVisibility() != GONE) {// 当子控件为 wrap_contentmeasureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);... ...}}... ...// 设置 FrameLayout 的宽高setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT));... ...//  自身是不确定大小的模式,子view又是MATCH_PARENT属性的,就需要为这些子view重新测量。for (int i = 0; i < count; i++) {if (lp.width == LayoutParams.MATCH_PARENT) {... ...childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);} else {childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,getPaddingLeftWithForeground() + getPaddingRightWithForeground() +lp.leftMargin + lp.rightMargin,lp.width);}final int childHeightMeasureSpec;if (lp.height == LayoutParams.MATCH_PARENT) {... ...childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);} else {childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,getPaddingTopWithForeground() + getPaddingBottomWithForeground() +lp.topMargin + lp.bottomMargin,lp.height);}// 子控件调用 measure,层层调用下去.child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}
}

widthMeasureSpec 与 heightMeasureSpec 是一个int值(java中int型变量由4个字节(32bit)组成),
其中 高2位 用来保存 MeasureMode,低30位 用来保存 size.
MeasureSpec有3种模式:

  • UNSPECIFIED # 父容器不对子view的大小做限制,一般用于系统内部,或者ListView ScrollView等滑动控件。
  • EXACTLY # 父控件已为子控件确定了一个确切的大小,孩子将被给予这些界限,不管子控件自己希望的是多大,对应于 match_parent 和 具体的值(比如 android:layout_width=“200px”),父容器测量出 View所需要的大小,也就是SpecSize的值。
  • AT_MOST # 在此模式下,父容器未能检测出子view的大小,但指定了一个最大大小spec size,子view的大小不能超过此值。父控件会给子控件尽可能大的尺寸,对应于 wrap_comtent, 子view 的最终大小是父View指定的SpecSize值, 并且子view的大小小于这个值.

MeasureSpec 常用的三个函数:

  • makeMeasureSpec # 根据提供的 size 和 mode 创建一个 测量值
  • getMode # 从所提供的测量值 中 提取 模式mode
  • getSize # 从所提供的测量值 中提取 尺寸size
View:
setMeasuredDimension // 设置宽高的,基本上都会使用,很多自定义控件或者Android原生控件
measureresolveSizeAndState //
getDefaultSize  //
combineMeasuredStatesView.MeasureSpec:
MeasureSpec.makeMeasureSpec // 根据所提供的size和mode创建一个测量规范ViewGroup:
measureChildWithMargins  // 子view,多宽,多高, 内部加上了viewGroup的padding值、子view的margin值和传入的宽高wUsed、hUsed  (padding + margin + used) ,把 margin 及  padding 也作为子视图大小的一部分
measureChild  // 某一个子view,多宽,多高, 内部加上了viewGroup的padding值
measureChildren // 循环的调用所有子child的 measureChild
getChildMeasureSpec // 给子view 计算出正确的 MeasureSpecRecyclerView: 为了适应自己的那套东西,重写了很多函数,比如 getChildMeasureSpec等,也新增了很多函数,比如 measureChildWithMargins等// 在自定义控件的时候,就看自己的需求和定义吧,调用相应的函数或者自己重写,新增都可以.

getChildMeasureSpec 的规则(具体可以查看源码,了解下就好了,调用就行了)


<FrameLayoutandroid:background="#000000"// android:padding="50px"android:layout_width="500px"android:layout_height="500px"><Buttonandroid:text="textTest"// android:layout_margin="50px"// android:layout_width="wrap_content"// android:layout_height="wrap_content" android:layout_width="match_parent"android:layout_height="match_parent" />
</FrameLayout>// FrameLayout.onMeasure->measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
protected void measureChildWithMargins(View child,int parentWidthMeasureSpec, int widthUsed,int parentHeightMeasureSpec, int heightUsed) {final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin+ widthUsed, lp.width);... ...child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}// 加上父控件的 mPaddingLeft + mPaddingRight + 还有子控件的 lp.leftMargin + lp.rightMargin
// 传入 getChildMeasureSpec padding参数.
// specSize - padding,得到子控件 初步的 size,还需要 用 specMode 进行相应的操作,
// 最后才会选择 size 还是 childDimension.
// 假设前提: 并且父控件有确切的大小,属于 MeasureSpec.EXACTLY
// 1. 没有 padding, margin,子控件为 match_parent,那么子控件 resultSize = 500px; resultMode = MeasureSpec.EXACTLY;
// 2. 父控件的 padding, button 的 margin 为 50px,那么子控件 resultSize = 300px; resultMode = MeasureSpec.EXACTLY;
// 3. 没有 padding, marigin,子控件为 wrap_content,那么子控件 resultSize = 500px; resultMode = MeasureSpec.AT_MOST;
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {int specMode = MeasureSpec.getMode(spec);int specSize = MeasureSpec.getSize(spec);int size = Math.max(0, specSize - padding);int resultSize = 0;int resultMode = 0;switch (specMode) {case MeasureSpec.EXACTLY: if (childDimension >= 0) {resultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {resultSize = size;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.WRAP_CONTENT) {resultSize = size;resultMode = MeasureSpec.AT_MOST;}break;... ...}return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}// 默认 View 的 onMeasure
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

没有 padding, marigin,子控件为 wrap_content,
那么子控件 resultSize = 500px; resultMode = MeasureSpec.AT_MOST 的 demo.
为何 button不占满全屏,因为传递给 button 的 widthMeasureSpec 或 heightMeasureSpec,基本属于 Size=500px, mode=MeasureSpec.AT_MOST.
因为button继承的 TextView,内部的 onMeasure 进行了处理的(代码可以自行查看).

// 我将 button 的 widthMeasureSpec 与 heightMeasureSpec 进行了一个处理
public class TestButton extends Button {... ...protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}}// 看了代码我们知道,父控件给 修改后的 TestButton 传递的 mode 是 MeasureSpec.AT_MOST,size 为 500px,
// 因为 getDefaultSize 进行了处理,result = specSize; 所以最后占满了父控件.
public static int getDefaultSize(int size, int measureSpec) {int result = size;int specMode = MeasureSpec.getMode(measureSpec);int specSize = MeasureSpec.getSize(measureSpec);switch (specMode) {case MeasureSpec.UNSPECIFIED:result = size;break;case MeasureSpec.AT_MOST:case MeasureSpec.EXACTLY:result = specSize;break;}return result;
}

最后 button占满了父控件

getWidth()和getMeasuredWidth()的区别
getMeasuredWidth(): 只要一执行完 setMeasuredDimension()方法, 就有值了, 并且不再改变.

getWidth(): 必须执行完 onMeasure() 才有值, 可能发生改变.

如果 onLayout 没有对子 View 实际显示的宽高进行修改, 那么 getWidth() 的值 == getMeasuredWidth() 的值.

1.2. View布局layout

measure完成之后,就会调用 layout.
从 ViewRootImpl 的 performTraversals->performLayout 函数开始,然后 DecorView.layout

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

layout相对 measure 操作就简单一些了,将子控件放在合适的位置上.

// View.java
public void layout(int l, int t, int r, int b) {... ...// 1. 调用 setFrame 位置保存起来,设定 view的四个位置(left, right, top, bottom).boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);... ...if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {// 2. 子控件进行位置分配,一般是 继承ViewGroup后的控件 实现onLayout,比如FrameLayout等.onLayout(changed, l, t, r, b);// 3. 清除 PFLAG_LAYOUT_REQUIRED 标识,layout完成了操作.mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;... ...}
}// ViewGroup.java 将 onLayout 设置成了 abstract 类型,所以,继承 ViewGroup 都需要实现 onLayout,比如 FrameLayout等.
protected abstract void onLayout(boolean changed, int l, int t, int r, int b);// FrameLayout.java
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {... ...for (int i = 0; i < count; i++) {final View child = getChildAt(i);if (child.getVisibility() != GONE) {... ...child.layout(childLeft, childTop, childLeft + width, childTop + height);}}... ...
}

看下整体的流程,不断的一层层往下调用.

这里就不过的讲解 layout, onLayout 具体可以参考 Android原生继承了 ViewGroup 实现 onLayout的控件.

1.3. View绘制流程

从 ViewRootImpl 的 performTraversals->performDraw 函数开始,然后 DecorView.draw

// DecorView,调用了 View 的 draw 函数.
@Overridepublic void draw(Canvas canvas) {super.draw(canvas);... ...}

看看 View.draw 函数做了一些什么事情,具体关注4个关键的函数.

1. 绘制背景(颜色值或图片,Drawable对象,比如 Shader, DrawableState等),
调用 setBackgroundColor, setBackgroundDrawable, setBackgroundResouce设置不同的背景.
2. 绘制自身内容,比如 ImageView(绘制图片), TextView(绘制文字), LinearLayout(Divider 分割线)
3. 绘制子控件,遍历所有子控件的 draw方法,一层层调用下去.
4. 绘制装饰 (foreground, scrollbars)public void draw(Canvas canvas) {if (!dirtyOpaque) {drawBackground(canvas); // 1. 绘制背景}... ...if (!verticalEdges && !horizontalEdges) {if (!dirtyOpaque) onDraw(canvas); // 2. 绘制自身内容dispatchDraw(canvas); // 3. 绘制子控件... ...onDrawForeground(canvas); // 4. 绘制装饰... ...}... ...
}

虚线代表了 Override,比如 ImageView

TV开发经常要对 继承了ViewGroup上面onDraw绘制东西,是无法显示出来的,
是因为在Viewgroup 调用 initViewGroup,函数里面 setFlags(WILLL_NOT_DRAW,DRAW_MASK),相当于调用了setWillNotDraw(true),
如果想绘制内容->onDraw,使用 setWillNotDraw(false) 才能显示内容出来.
这就是为何在一些继承了ViewGroup的自定义控件上,想要绘制阴影,内容等 无法显示出来的原因以及解决方案.

// ViewGroup.java
private void initViewGroup() {setFlags(WILL_NOT_DRAW, DRAW_MASK);... ...
}

TV开发中还会遇到 放大被挡住的问题,如何进行处理:
第一种方式 修改绘制顺序:

//ViewGroup.java
setChildrenDrawingOrderEnabled(true);private int position = 0;// 也可以重写此函数,bringToFront 调用的绘制顺序就被更改.
public void bringChildToFront(ViewGroup vg, View child) {position = vg.indexOfChild(child);if (position != -1) {vg.postInvalidate();}
}/*** 此函数 dispatchDraw 中调用. <br>* 原理就是和最后一个要绘制的view,交换了位置. <br>* 因为dispatchDraw最后一个绘制的view是在最上层的. <br>* 这样就避免了使用 bringToFront 导致焦点错乱问题. <br>*/
public int getChildDrawingOrder(int childCount, int i) {if (position != -1) {if (i == childCount - 1) {return position;}if (i == position) {return childCount - 1;}}return i;
}RecyclerView的绘制顺序,可以参考Leanback的.
int getChildDrawingOrder(RecyclerView recyclerView, int childCount, int i) {View view = findViewByPosition(mFocusPosition);if (view == null) {return i;}int focusIndex = recyclerView.indexOfChild(view);// supposely 0 1 2 3 4 5 6 7 8 9, 4 is the center item// drawing order is 0 1 2 3 9 8 7 6 5 4if (i < focusIndex) {return i;} else if (i < childCount - 1) {return focusIndex + childCount - 1 - i;} else {return focusIndex;}
}

第二种方式调用:bringToFront 函数. 这个函数建议尽量不使用,好像有问题.

1.3.1 绘制例子

  • draw,一般很少会去在上面做一些绘制的事情,除非有特定的需要,比如你绘制的东西需要在背景以及子控件之前.
  • onDraw,不必多说,比如LinearLayout的分割线等等,TV上的一些自绘的内容或者其它.
  • 真正的圆角控件,dispatchDraw
protected void dispatchDraw(Canvas canvas) {canvas.saveLayer(new RectF(0, 0, canvas.getWidth(),canvas.getHeight()), null, Canvas.ALL_SAVE_FLAG);// 绘制子控件super.dispatchDraw(canvas); //目的,需要显示的内容// 绘制带有圆角的 Path//roundPaint.setColor(Color.WHITE);roundPaint.setAntiAlias(true);// 绘制模式为填充roundPaint.setStyle(Paint.Style.FILL);// 混合模式为 DST_IN, 即仅显示当前绘制区域和背景区域交集的部分,并仅显示背景内容。roundPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));Path mPath = new Path();float r = 300;PointF center = new PointF(canvas.getWidth() / 2, canvas.getHeight()  / 2);mPath.addCircle(center.x, center.y, r, Path.Direction.CW);// 圆角的矩形,使用此方法,addRoundRectmPath.moveTo(0, 0);  // 通过空操作让Path区域占满画布mPath.moveTo(canvas.getWidth(), canvas.getHeight());canvas.drawPath(mPath, roundPaint); // 源内容,用于遮罩层.canvas.restore();
}

子view集合,只在圆形里面显示.


与属性动画结合的绘制方法 参考 AndroidTvwidget 的 AnimView 的一个 demo.
https://gitee.com/kumei/android-tv-frame-new/blob/develop/AndroidTvWidget/app/src/main/java/com/open/test/view/AnimView.java

1.3.2 动画的原理简单了解

属性动画 关键的两个类ObjectAnimator和ValueAnimator
ObjectAnimator继承了ValueAnimator,ObjectAnimator#start(),ValueAnimator#start()
ValueAnimator.doAnimationFrame,animationFrame中将调用animateValue

void animateValue(float fraction) {fraction = mInterpolator.getInterpolation(fraction); // 插值器mCurrentFraction = fraction;int numValues = mValues.length;for (int i = 0; i < numValues; ++i) {mValues[i].calculateValue(fraction);}if (mUpdateListeners != null) {int numListeners = mUpdateListeners.size();for (int i = 0; i < numListeners; ++i) {mUpdateListeners.get(i).onAnimationUpdate(this); // 最后调用}}
}

最后调用 get与set方法(比如 setXXX getXXX)将会被调用属性(通过反射进行调用)的函数
比如 setTranslationX 或者 setScaleY 的调用,是一个什么过程,了解下

最后还是调用 invalidate// ViewRootImpl.java,最后调用 scheduleTraversalsvoid scheduleTraversals() {if (!mTraversalScheduled) {mTraversalScheduled = true;//暂停了handler的后续消息处理,防止界面刷新的时候出现同步问题mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();//将runnable发送给handler执行//performTraversals 在一个runnable中被调用的,通过将这个runnable加入队列来执行//performTraversals就是开头讲的 performMeasure->measure,performLayout->layout,performDraw->draw 的开始mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);if (!mUnbufferedInputDispatch) {scheduleConsumeBatchedInput();}notifyRendererOfFramePending();pokeDrawLockIfNeeded();}
}

2. 自定义控件了解

2.1 自定义属性

    public NewFrameLayout(Context context) {this(context, null);}public NewFrameLayout(Context context, AttributeSet attrs) {this(context, attrs, 0);// 这里可以配合主题定义自己的属性// 比如 AppTheme.Light,里面的 <item name="cardViewStyle">@style/CardViewStyle.Light</item>// this(context, attrs, attr.NewFrameLayoutStyle);}public NewFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.NewFrameLayout, defStyleAttr, defStyleRes);... ...a.recycle(); // 这个不要忘记写}

2.2 组合控件

参考 tvWidget 里面的多状态UI控件,了解下.

public class TVUICommonItemView extends RelativeLayout {
}xml 布局使用 多个控件组合一起使用
<merge xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"><!-- 左侧图标 --><ImageView />
</merge>

2.3 属性修改

简单的看一下.
又比如 LinearLayout 这些 Android原生的控件,还有网上的一些自定义控件,比如 FlowLayout.

比如 RecyclerView,addChild… 修改了 onMeasure,onLayout,onDraw,draw 来满足自己的一些特定需求.
LayoutManager : onLayoutChildren,measureChildWithMargins 等等.
如果想做一些相关自定义的 LayoutManager,只需要关注 LayoutManager的几个重要函数.:onLayoutChildren

https://blog.csdn.net/u010072711/article/details/78867096 图文详解LinearLayoutManager填充、测量、布局过程
https://blog.csdn.net/qibin0506/article/details/52676670?locationNum=2 RecyclerView自定义LayoutManager,打造不规则布局
https://www.jianshu.com/p/61a4811bb5ca 最清晰的 RecyclerView 使用及源码解析
https://juejin.im/entry/59c0ccfd6fb9a00a3c4b16d9 一个有特点的正六边形RecyclerView—HexagonRecyclerView详解篇
https://blog.csdn.net/u011387817/article/details/81875021 Android自定义LayoutManager第十一式之飞龙在天
https://github.com/JadynAi/InfinateCard 卡牌堆叠滑动效果,增加回滚动画
https://github.com/leochuan/ViewPagerLayoutManager ViewPager-LayoutManager
https://www.jianshu.com/p/715b59c46b74 你可能误会了!原来自定义LayoutManager可以这么简单
https://blog.csdn.net/lylodyf/article/details/52846602 自定义LayoutManager的详解及其使用

2.4 注意事项

onDraw或者相关绘制的函数中 避免重复创建变量,内存会抖动.
在 onDetachedFromWindow 做一些销毁的事情,避免出现不必要的麻烦… …
最好按照谷歌的制定的标准规范来写.
组合控件的布局建议使用 merge,减少层级.

3. 参考资料

谷歌源码 Android-24,Android内核剖析,Android 开发艺术探索 等等.
自定义控件测量模式真的和 match_parent,wrap_content 一一对应吗?

Androd - 自定义view了解与应用相关推荐

  1. Android自定义View基本步骤

    一.自定义属性 1.在res下的values下面新建attrs.xml 2.在布局中使用,声明命名空间 3.在自定义View构造方法中通过TypedArray获取属性 4.必须回收 array.rec ...

  2. Android自定义View —— TypedArray

    在上一篇中Android 自定义View Canvas -- Bitmap写到了TypedArray 这个属性 下面也简单的说一下TypedArray的使用 TypedArray 的作用: 用于从该结 ...

  3. Android 自定义View Canvas —— Bitmap

    Bitmap 绘制图片 常用的方法有一下几种 (1) drawBitmap(@NonNull Bitmap bitmap, float left, float top, @Nullable Paint ...

  4. Android 自定义View —— Canvas

    上一篇在android 自定义view Paint 里面 说了几种常见的Point 属性 绘制图形的时候下面总有一个canvas ,Canvas 是是画布 上面可以绘制点,线,正方形,圆,等等,需要和 ...

  5. Android 自定义View —— Paint

    上一篇说了自定义view的坐标系以及view 的使用,下面说下自定义view Paint 的使用 Paint 相对于画笔 ,可以使用Paint 来决定画的内容的颜色,边距粗细,设置样式,字体大小 ,等 ...

  6. Android 自定义View (入门 篇) 的使用

    每次都是过了很久都需要温习一下,自己打算整理一下方便查阅, 自定义view 首选需要明白的就是它的坐标系了,以手机左上角为起始点(0.0),横向的为x轴,竖向的为y轴 为了更好的理解我画了一幅草图如下 ...

  7. 28自定义View 模仿联系人字母侧栏

    自定义View LetterView.java package com.qf.sxy.customview02;import android.content.Context; import andro ...

  8. android炫酷的自定义view,Android自定义View实现炫酷进度条

    本文实例为大家分享了Android实现炫酷进度条的具体代码,供大家参考,具体内容如下 下面我们来实现如下效果: 第一步:创建attrs文件夹,自定义属性: 第二步:自定义View: /** * Cre ...

  9. android 自定义音乐圆形进度条,Android自定义View实现音频播放圆形进度条

    本篇文章介绍自定义View配合属性动画来实现如下的效果 实现思路如下: 根据播放按钮的图片大小计算出圆形进度条的大小 根据音频的时间长度计算出圆形进度条绘制的弧度 通过Handler刷新界面来更新圆形 ...

最新文章

  1. [SQL] 查找数据库中含有某字段的所有表
  2. File,FileInputStream,FileReader,InputStreamReader,BufferReader 的区别使用
  3. python练手_Python数据分析练手项目
  4. 阿里云黄海宇:窄带高清2.0——让直播更惊艳的魔术
  5. 内存条上面参数详解_价格极低的国产颗粒内存条:光威弈系列Pro评测,超频表现超稳定...
  6. 计算机辅助建筑制图规范,房屋建筑制图统一标准 [附条文说明] GB/T50001-2017
  7. Unity3D(四)Camera和SkyBox
  8. kibana4 分析和搜索仪表板 安装和配置
  9. GridView 对列进行排序
  10. 解读数字孪生概念 —— 智慧城市大脑
  11. Android WIFI认证的流程
  12. 五款堪称神奇的手机APP 一定不要错过了
  13. Win10《芒果TV》更新v3.8.30流星版:优化稳定性、升级无边框播放体验
  14. 安卓代码播放手机本地视频
  15. 聊天机器人chatbot搭建及思考(TensorFlow)(附代码)
  16. 苹果系统无法购买服务器,梦幻西游手游iOS目前无法处理您的购买解决办法
  17. Nagios学习笔记
  18. java编程思想学习笔记(第七章:复用类)
  19. NSUserDefaults见解
  20. Algorithms - Lecture 12 - Randomized Algorithms

热门文章

  1. linux取消管理员权限设置,Linux添加用户并赋予/取消管理员权限
  2. 1秒把 FLV MOV AVI MKV 3GP WEBM 转去 MP4 完全免费 - 完美教程 超级简单 你没看错
  3. BZOJ4451 : [Cerc2015]Frightful Formula
  4. 【路由交换01】什么是路由交换?
  5. ListView所扩展的各种牛叉效果
  6. 一文带你全面解析postman工具的使用(效率篇)
  7. CAD参数绘制图案填充(网页版)
  8. 【PHP工作记录】国内18位身份证校验
  9. 致癌秘密何以在矿泉水里隐藏10年之久
  10. 超链接的hideFocus属性