Androd - 自定义view了解与应用
文章目录
- 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了解与应用相关推荐
- Android自定义View基本步骤
一.自定义属性 1.在res下的values下面新建attrs.xml 2.在布局中使用,声明命名空间 3.在自定义View构造方法中通过TypedArray获取属性 4.必须回收 array.rec ...
- Android自定义View —— TypedArray
在上一篇中Android 自定义View Canvas -- Bitmap写到了TypedArray 这个属性 下面也简单的说一下TypedArray的使用 TypedArray 的作用: 用于从该结 ...
- Android 自定义View Canvas —— Bitmap
Bitmap 绘制图片 常用的方法有一下几种 (1) drawBitmap(@NonNull Bitmap bitmap, float left, float top, @Nullable Paint ...
- Android 自定义View —— Canvas
上一篇在android 自定义view Paint 里面 说了几种常见的Point 属性 绘制图形的时候下面总有一个canvas ,Canvas 是是画布 上面可以绘制点,线,正方形,圆,等等,需要和 ...
- Android 自定义View —— Paint
上一篇说了自定义view的坐标系以及view 的使用,下面说下自定义view Paint 的使用 Paint 相对于画笔 ,可以使用Paint 来决定画的内容的颜色,边距粗细,设置样式,字体大小 ,等 ...
- Android 自定义View (入门 篇) 的使用
每次都是过了很久都需要温习一下,自己打算整理一下方便查阅, 自定义view 首选需要明白的就是它的坐标系了,以手机左上角为起始点(0.0),横向的为x轴,竖向的为y轴 为了更好的理解我画了一幅草图如下 ...
- 28自定义View 模仿联系人字母侧栏
自定义View LetterView.java package com.qf.sxy.customview02;import android.content.Context; import andro ...
- android炫酷的自定义view,Android自定义View实现炫酷进度条
本文实例为大家分享了Android实现炫酷进度条的具体代码,供大家参考,具体内容如下 下面我们来实现如下效果: 第一步:创建attrs文件夹,自定义属性: 第二步:自定义View: /** * Cre ...
- android 自定义音乐圆形进度条,Android自定义View实现音频播放圆形进度条
本篇文章介绍自定义View配合属性动画来实现如下的效果 实现思路如下: 根据播放按钮的图片大小计算出圆形进度条的大小 根据音频的时间长度计算出圆形进度条绘制的弧度 通过Handler刷新界面来更新圆形 ...
最新文章
- [SQL] 查找数据库中含有某字段的所有表
- File,FileInputStream,FileReader,InputStreamReader,BufferReader 的区别使用
- python练手_Python数据分析练手项目
- 阿里云黄海宇:窄带高清2.0——让直播更惊艳的魔术
- 内存条上面参数详解_价格极低的国产颗粒内存条:光威弈系列Pro评测,超频表现超稳定...
- 计算机辅助建筑制图规范,房屋建筑制图统一标准 [附条文说明] GB/T50001-2017
- Unity3D(四)Camera和SkyBox
- kibana4 分析和搜索仪表板 安装和配置
- GridView 对列进行排序
- 解读数字孪生概念 —— 智慧城市大脑
- Android WIFI认证的流程
- 五款堪称神奇的手机APP 一定不要错过了
- Win10《芒果TV》更新v3.8.30流星版:优化稳定性、升级无边框播放体验
- 安卓代码播放手机本地视频
- 聊天机器人chatbot搭建及思考(TensorFlow)(附代码)
- 苹果系统无法购买服务器,梦幻西游手游iOS目前无法处理您的购买解决办法
- Nagios学习笔记
- java编程思想学习笔记(第七章:复用类)
- NSUserDefaults见解
- Algorithms - Lecture 12 - Randomized Algorithms