点击上方 "程序员小乐"关注, 星标或置顶一起成长

关注订阅号「程序员小乐」,收看更多精彩内容

每日英文

If you're brave enough to say GOODBYE, life will reward you with a new HELLO.

只要你勇敢地说出再见,生活一定会赐予你一个新的开始。

每日掏心

人与人之间的很多矛盾都是从傲慢中来的,学会把自己的心态放低,放平,多看自己的缺点,多看别人的优点,让傲慢的心变得谦虚,恭敬,所处的环境自然就融洽了。

来自:未扬帆的小船 | 责编:乐乐

链接:juejin.im/user/57d822c90bd1d000585cad57

程序员小乐(ID:study_tech)第 1009 次推文

往日回顾: 《最受欢迎的女友职业排行榜Top10》

     

   正文   

/   前言   /

本文用于记录自定义View的基础步骤以及一些基础的信息,后期可能针对具体的点写一些补充性的文章。

/   View中关于四个构造函数参数   /

自定义View中View的构造函数有四个。

//  主要是在java代码中生命一个View时所调用,没有任何参数,一个空的View对象
public ChildrenView(Context context) {super(context);
}
// 在布局文件中使用该自定义view的时候会调用到,一般会调用到该方法
public ChildrenView(Context context, AttributeSet attrs) {this(context, attrs,0);
}
//如果你不需要View随着主题变化而变化,则上面两个构造函数就可以了
//下面两个是与主题相关的构造函数
public ChildrenView(Context context, AttributeSet attrs, int defStyleAttr) {this(context, attrs, defStyleAttr, 0);
}
//
public ChildrenView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes);
}

四个参数解释:

  • context:上下文

  • AttributeSet attrs:从xml中定义的参数

  • intdefStyleAttr:主题中优先级最高的属性

  • intdefStyleRes:优先级次之的内置于View的style(这里就是自定义View设置样式的地方)

多个地方定义属性,优先级排序 Xml直接定义 > xml中style引用 > defStyleAttr>defStyleRes > theme直接定义。

/   自定义属性说明   /

除了基本类型的不说 讲一下其它几个吧:

  • color:引用颜色

  • dimension:引用字体大小

//定义
<attr name = "text_size" format = "dimension" />
//使用:app:text_size = "28sp"
或者 app:text_size = "@android:dimen/app_icon_size"
  • enum:枚举值

//定义<attr name="orientation"><enum name="horizontal" value="0" /><enum name="vertical" value="1" /></attr>
//使用:app:orientation = "vertical"
  • flags:标志 (位或运行) 主要作用=可以多个值

//定义<attr name="gravity"><flag name="top" value="0x01" /><flag name="bottom" value="0x02" /><flag name="left" value="0x04" /><flag name="right" value="0x08" /><flag name="center_vertical" value="0x16" /></attr>
// 使用
app:gravity = Top|left
  • fraction:百分数

//定义:
<attr name = "transparency" format = "fraction" />
//使用:app:transparency = "80%" 
  • reference:参考/引用某一资源ID

//定义:<attr name="leftIcon" format="reference" />
//使用:
app:leftIcon = "@drawable/图片ID"
  • 混合类型:属性定义时指定多种类型值

//属性定义<attr name = "background" format = "reference|color" />
//使用
android:background = "@drawable/图片ID"
//或者
android:background = "#FFFFFF"

/   自定义控件类型   /

自定义组合控件步骤

自定义属性

在res/values目录下的attrs.xml文件中。

<resources>
<declare-styleable name="CustomView"><attr name="leftIcon" format="reference" /><attr name="state" format="boolean"/><attr name="name" format="string"/></declare-styleable>
</resources>
布局中使用自定义属性

在布局中使用。

<com.myapplication.view.CustomViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"app:leftIcon="@mipmap/ic_temp"app:name="温度"app:state="false" />
view的构造函数获取自定义属性
class DigitalCustomView : LinearLayout {constructor(context: Context) : super(context)constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {LayoutInflater.from(context).inflate(R.layout.view_custom, this)var ta = context.obtainStyledAttributes(attrs, R.styleable.CustomView)mIcon = ta.getResourceId(R.styleable.CustomView_leftIcon, -1) //左图像mState = ta.getBoolean(R.styleable.DigitalCustomView_state, false)mName = ta.getString(R.styleable.CustomView_name)ta.recycle()initView()}}

上面给出大致的代码,记得获取context.obtainStyledAttributes(attrs, R.styleable.CustomView),最后要释放掉ta.recycle()。

继承系统控件

就是继承系统已经提供好给我们的控件例如TextView、LinearLayout等,分为View类型或者ViewGroup类型的两种。主要根据业务需求进行实现,实现重写的空间也很大,主要看需求。比如需求 :在文字后面加个颜色背景。

搜索公众号程序员小乐回复关键字“Java”,获取Java面试题和答案。

根据需要一般这种情况下我们是希望可以复用系统的onMeaseur和onLayout流程.直接复写onDraw方法。

class Practice02BeforeOnDrawView : AppCompatTextView {internal var paint = Paint(Paint.ANTI_ALIAS_FLAG)internal var bounds = RectF()constructor(context: Context) : super(context) {}constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {}constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {}init {paint.color = Color.parseColor("#FFC107")}override fun onDraw(canvas: Canvas) {// 把下面的绘制代码移到 super.onDraw() 的上面,就可以让原主体内容盖住你的绘制代码了// (或者你也可以把 super.onDraw() 移到这段代码的下面)val layout = layoutbounds.left = layout.getLineLeft(1)bounds.right = layout.getLineRight(1)bounds.top = layout.getLineTop(1).toFloat()bounds.bottom = layout.getLineBottom(1).toFloat()//绘制方形背景canvas.drawRect(bounds, paint)super.onDraw(canvas)}
}

这里会涉及到画笔Paint()、画布canvas、路径Path、绘画顺序等的一些知识点,后面再详细说明。

直接继承View

这种就是类似TextView等,不需要去轮训子View只需要根据自己的需求重写onMeasure()、onLayout()、onDraw()等方法便可以,要注意点就是记得Padding等值要记得加入运算。

 private int getCalculateSize(int defaultSize, int measureSpec) {int finallSize = defaultSize;int mode = MeasureSpec.getMode(measureSpec);int size = MeasureSpec.getSize(measureSpec);//  根据模式对switch (mode) {case MeasureSpec.EXACTLY: {...break;}case MeasureSpec.AT_MOST: {...break;}case MeasureSpec.UNSPECIFIED: {...break;}}return finallSize;
}@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);int width = getCalculateSize(120, widthMeasureSpec);int height = getCalculateSize(120, heightMeasureSpec);setMeasuredDimension(width, height);
}//画一个圆@Overrideprotected void onDraw(Canvas canvas) {//调用父View的onDraw函数,因为View这个类帮我们实现了一些基本的而绘制功能,比如绘制背景颜色、背景图片等super.onDraw(canvas);int r = getMeasuredWidth() / 2;//圆心的横坐标为当前的View的左边起始位置+半径int centerX = getLeft() + r;//圆心的纵坐标为当前的View的顶部起始位置+半径int centerY = getTop() + r;Paint paint = new Paint();paint.setColor(Color.RED);canvas.drawCircle(centerX, centerY, r, paint);}

直接继承ViewGroup

类似实现LinearLayout等,可以去看那一下LinearLayout的实现 基本的你可能要重写onMeasure()、onLayout()、onDraw()方法,这块很多问题要处理,包括轮训childView的测量值以及模式进行大小逻辑计算等,这个篇幅过大后期加多个文章写详细的。这里写个简单的需求,模仿LinearLayout的垂直布局。

 class CustomViewGroup :ViewGroup{constructor(context:Context):super(context)constructor(context: Context,attrs:AttributeSet):super(context,attrs){//可获取自定义的属性等}override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {super.onMeasure(widthMeasureSpec, heightMeasureSpec)//将所有的子View进行测量,这会触发每个子View的onMeasure函数measureChildren(widthMeasureSpec, heightMeasureSpec)val widthMode = MeasureSpec.getMode(widthMeasureSpec)val widthSize = MeasureSpec.getSize(widthMeasureSpec)val heightMode = MeasureSpec.getMode(heightMeasureSpec)val heightSize = MeasureSpec.getSize(heightMeasureSpec)val childCount = childCountif (childCount == 0) {//没有子View的情况setMeasuredDimension(0, 0)} else {//如果宽高都是包裹内容if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {//我们将高度设置为所有子View的高度相加,宽度设为子View中最大的宽度val height = getTotalHeight()val width = getMaxChildWidth()setMeasuredDimension(width, height)} else if (heightMode == MeasureSpec.AT_MOST) {//如果只有高度是包裹内容//宽度设置为ViewGroup自己的测量宽度,高度设置为所有子View的高度总和setMeasuredDimension(widthSize, getTotalHeight())} else if (widthMode == MeasureSpec.AT_MOST) {//如果只有宽度是包裹内容//宽度设置为子View中宽度最大的值,高度设置为ViewGroup自己的测量值setMeasuredDimension(getMaxChildWidth(), heightSize)}}/**** 获取子View中宽度最大的值*/private fun getMaxChildWidth(): Int {val childCount = childCountvar maxWidth = 0for (i in 0 until childCount) {val childView = getChildAt(i)if (childView.measuredWidth > maxWidth)maxWidth = childView.measuredWidth}return maxWidth}/**** 将所有子View的高度相加*/private fun getTotalHeight(): Int {val childCount = childCountvar height = 0for (i in 0 until childCount) {val childView = getChildAt(i)height += childView.measuredHeight}return height}}override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {val count = childCountvar currentHeight = tfor (i in 0 until count) {val child = getChildAt(i)val h = child.measuredHeightval w = child.measuredWidth//摆放子viewchild.layout(l, currentHeight, l + w, currentHeight + h)currentHeight += h}}
}

主要两点 先 measureChildren()轮训遍历子View获取宽高,并根据测量模式逻辑计算最后所有的控件的所需宽高,最后setMeasuredDimension()保存一下。

/   绘制流程相关知识点   /

View的绘制流程相关 最基本的三个相关函数 measure() ->layout()->draw()。

/   onMeasure()相关知识点   /

MeasureSpec

MeasureSpec是View的内部类,它封装了一个View的尺寸,在onMeasure()当中会根据这个MeasureSpec的值来确定View的宽高。MeasureSpec 的数据是int类型,有32位。高两位表示模式,后面30位表示大小size。则MeasureSpec = mode+size三种模式分别为:EXACTLY,AT_MOST,UNSPECIFIED

  • EXACTLY: (match_parent或者 精确数据值)精确模式,对应的数值就是MeasureSpec当中的size。

  • AT_MOST:(wrap_content)最大值模式,View的尺寸有一个最大值,View不超过MeasureSpec当中的Size值。

  • UNSPECIFIED:(一般系统使用)无限制模式,View设置多大就给他多大。

//获取测量模式val widthMode = MeasureSpec.getMode(widthMeasureSpec)
//获取测量大小
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
//通过Mode和Size构造MeasureSpec
val measureSpec = MeasureSpec.makeMeasureSpec(size, mode);

View #onMeasure()源码

     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}protected int getSuggestedMinimumWidth() {return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());}protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {boolean optical = isLayoutModeOptical(this);if (optical != isLayoutModeOptical(mParent)) {Insets insets = getOpticalInsets();int opticalWidth  = insets.left + insets.right;int opticalHeight = insets.top  + insets.bottom;measuredWidth  += optical ? opticalWidth  : -opticalWidth;measuredHeight += optical ? opticalHeight : -opticalHeight;}setMeasuredDimensionRaw(measuredWidth, measuredHeight);}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;}private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {mMeasuredWidth = measuredWidth;mMeasuredHeight = measuredHeight;mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;}

setMeasuredDimension(int measuredWidth, int measuredHeight) :用来设置View的宽高,在我们自定义View保存宽高也会要用到。

getSuggestedMinimumWidth():当View没有设置背景时,默认大小就是mMinWidth,这个值对应Android:minWidth属性,如果没有设置时默认为0. 如果有设置背景,则默认大小为mMinWidth和mBackground.getMinimumWidth()当中的较大值。

getDefaultSize(int size, int measureSpec):用来获取View默认的宽高,在**getDefaultSize()**中对MeasureSpec.AT_MOST,MeasureSpec.EXACTLY两个的处理是一样的,我们自定义View的时候 要对两种模式进行处理。

ViewGroup中并没有measure()也没有onMeasure()

因为ViewGroup除了测量自身的宽高,还需要测量各个子View的宽高,不同的布局测量方式不同 (例如 LinearLayout跟RelativeLayout等布局),所以直接交由继承者根据自己的需要去复写。但是里面因为子View的测量是相对固定的,所以里面已经提供了基本的measureChildren()以及measureChild()来帮助我们对子View进行测量。

/   onLayout相关   /

View.java的onLayout方法是空实现:因为子View的位置,是由其父控件的onLayout方法来确定的。onLayout(int l, int t, int r, int b)中的参数l、t、r、b都是相对于其父 控件的位置。自身的mLeft, mTop, mRight, mBottom都是相对于父控件的位置。

Android坐标系

内部View坐标系跟点击坐标

看一下View#layout(int l, int t, int r, int b)源码

public void layout(int l, int t, int r, int b) {if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;}int oldL = mLeft;int oldT = mTop;int oldB = mBottom;int oldR = mRight;boolean changed = isLayoutModeOptical(mParent) ?setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {onLayout(changed, l, t, r, b);//   ....省略其它部分}private boolean setOpticalFrame(int left, int top, int right, int bottom) {Insets parentInsets = mParent instanceof View ?((View) mParent).getOpticalInsets() : Insets.NONE;Insets childInsets = getOpticalInsets();return setFrame(left   + parentInsets.left - childInsets.left,top    + parentInsets.top  - childInsets.top,right  + parentInsets.left + childInsets.right,bottom + parentInsets.top  + childInsets.bottom);}protected boolean setFrame(int left, int top, int right, int bottom) {boolean changed = false;// ....省略其它部分if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {changed = true;int drawn = mPrivateFlags & PFLAG_DRAWN;int oldWidth = mRight - mLeft;int oldHeight = mBottom - mTop;int newWidth = right - left;int newHeight = bottom - top;boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);invalidate(sizeChanged);mLeft = left;mTop = top;mRight = right;mBottom = bottom;mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);mPrivateFlags |= PFLAG_HAS_BOUNDS;if (sizeChanged) {sizeChange(newWidth, newHeight, oldWidth, oldHeight);}if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) {mPrivateFlags |= PFLAG_DRAWN;invalidate(sizeChanged);invalidateParentCaches();}mPrivateFlags |= drawn;mBackgroundSizeChanged = true;mDefaultFocusHighlightSizeChanged = true;if (mForegroundInfo != null) {mForegroundInfo.mBoundsChanged = true;}notifySubtreeAccessibilityStateChangedIfNeeded();}return changed;}

四个参数l、t、r、b分别代表View的左、上、右、下四个边界相对于其父View的距离。在调用onLayout(changed, l, t, r, b);之前都会调用到setFrame()确定View在父容器当中的位置,赋值给mLeft,mTop,mRight,mBottom。在ViewGroup#onLayout()跟View#onLayout()都是空实现,交给继承者根据自身需求去定位。

搜索公众号程序员小乐回复关键字“offer”,获取offer面试题和答案。

部分零散知识点:

getMeasureWidth()与getWidth()getMeasureWidth()返回的是mMeasuredWidth,而该值是在setMeasureDimension()中的setMeasureDimensionRaw()中设置的。因此onMeasure()后的所有方法都能获取到这个值。getWidth返回的是mRight-mLeft,这两个值,是在layout()中的setFrame()中设置的.getMeasureWidthAndState中有一句:This should be used during measurement and layout calculations only. Use {@link #getWidth()} to see how wide a view is after layout.

总结:只有在测量过程中和布局计算时,才用getMeasuredWidth()。在layout之后,用getWidth()来获取宽度。

/   draw()绘画过程   /

 /** Draw traversal performs several drawing steps which must be executed* in the appropriate order:**      1. Draw the background*      2. If necessary, save the canvas' layers to prepare for fading*      3. Draw view's content*      4. Draw children*      5. If necessary, draw the fading edges and restore layers*      6. Draw decorations (scrollbars for instance)*/

上面是draw()里面写的绘画顺序。

  1. 绘制背景。

  2. 如果必要的话,保存当前canvas

  3. 绘制View的内容

  4. 绘制子View

  5. 如果必要的话,绘画边缘重新保存图层

  6. 画装饰(例如滚动条)

看一下View#draw()源码的实现

public void draw(Canvas canvas) {// Step 1, draw the background, if neededint saveCount;if (!dirtyOpaque) {drawBackground(canvas);}// skip step 2 & 5 if possible (common case)final int viewFlags = mViewFlags;boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;if (!verticalEdges && !horizontalEdges) {// Step 3, draw the contentif (!dirtyOpaque) onDraw(canvas);// Step 4, draw the childrendispatchDraw(canvas);drawAutofilledHighlight(canvas);// Overlay is part of the content and draws beneath Foregroundif (mOverlay != null && !mOverlay.isEmpty()) {mOverlay.getOverlayView().dispatchDraw(canvas);}// Step 6, draw decorations (foreground, scrollbars)onDrawForeground(canvas);// Step 7, draw the default focus highlightdrawDefaultFocusHighlight(canvas);if (debugDraw()) {debugDrawFocus(canvas);}// we're done...return;}
}

由上面可以看到 先调用drawBackground(canvas) ->onDraw(canvas)->dispatchDraw(canvas)->onDrawForeground(canvas)越是后面绘画的越是覆盖在最上层。

drawBackground(canvas):画背景,不可重写

onDraw(canvas):画主体

  • 代码写在super.onDraw()前:会被父类的onDraw覆盖

  • 代码写在super.onDraw()后:不会被父类的onDraw覆盖

dispatchDraw() :绘制子 View 的方法

  • 代码写在super.dispatchDraw(canvas)前:把绘制代码写在 super.dispatchDraw() 的上面,这段绘制就会在 onDraw() 之后、 super.dispatchDraw() 之前发生,也就是绘制内容会出现在主体内容和子 View 之间。而这个…… 其实和重写 onDraw() 并把绘制代码写在 super.onDraw() 之后的做法,效果是一样的。

  • 代码写在super.dispatchDraw(canvas)后:只要重写 dispatchDraw(),并在 super.dispatchDraw() 的下面写上你的绘制代码,这段绘制代码就会发生在子 View 的绘制之后,从而让绘制内容盖住子 View 了。

onDrawForeground(canvas):包含了滑动边缘渐变和滑动条跟前景。

一般来说,一个 View(或 ViewGroup)的绘制不会这几项全都包含,但必然逃不出这几项,并且一定会严格遵守这个顺序。例如通常一个 LinearLayout 只有背景和子 View,那么它会先绘制背景再绘制子 View;一个 ImageView 有主体,有可能会再加上一层半透明的前景作为遮罩,那么它的前景也会在主体之后进行绘制。需要注意,前景的支持是在 Android 6.0(也就是 API 23)才加入的;之前其实也有,不过只支持 FrameLayout,而直到 6.0 才把这个支持放进了 View 类里。

注意事项

在 ViewGroup 的子类中重写除 dispatchDraw() 以外的绘制方法时,可能需要调用 setWillNotDraw(false);

出于效率的考虑,ViewGroup 默认会绕过 draw() 方法,换而直接执行 dispatchDraw(),以此来简化绘制流程。所以如果你自定义了某个 ViewGroup 的子类(比如 LinearLayout)并且需要在它的除 dispatchDraw() 以外的任何一个绘制方法内绘制内容,你可能会需要调用 View.setWillNotDraw(false) 这行代码来切换到完整的绘制流程(是「可能」而不是「必须」的原因是,有些 ViewGroup 是已经调用过 setWillNotDraw(false) 了的,例如 ScrollView)。

在重写的方法有多个选择时,优先选择 onDraw()

一段绘制代码写在不同的绘制方法中效果是一样的,这时你可以选一个自己喜欢或者习惯的绘制方法来重写。但有一个例外:如果绘制代码既可以写在 onDraw() 里,也可以写在其他绘制方法里,那么优先写在 onDraw() ,因为 Android 有相关的优化,可以在不需要重绘的时候自动跳过 onDraw() 的重复执行,以提升开发效率。享受这种优化的只有 onDraw() 一个方法。

/   在Activity中获取宽高   /

Activity获取view的宽高, 在onCreate , onResume等方法中获取到的都是0, 因为View的测量过程并不是和Activity的声明周期同步执行的。

view.postpost可以将一个runnable投递到消息队列的尾部,然后等待Looper调用此runnable的时候, View也已经初始化好了。

       view.post(new Runnable() {@Overridepublic void run() {int width = view.getMeasuredWidth();int height = view.getMeasuredHeight(); }});

ViewTreeObserver使用addOnGlobalLayoutListener接口, 当view树的状态发生改变或者View树内部的view的可见性发生改变时,onGlobalLayout都会被调用, 需要注意的是,onGlobalLayout方法可能被调用多次, 代码如下:

 view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {@Overridepublic void onGlobalLayout() {view.getViewTreeObserver().removeOnGlobalLayoutListener(this);int width = view.getMeasuredWidth();int height = view.getMeasuredHeight();}});

onWindowFocusChanged这个方法的含义是View已经初始化完毕了, 宽高已经准备好了, 需要注意的就是这个方法可能会调用多次, 在Activity onResume和onPause的时候都会调用, 也会有多次调用的情况。

     @Overridepublic void onWindowFocusChanged(boolean hasWindowFocus) {super.onWindowFocusChanged(hasWindowFocus);if(hasWindowFocus){int width = view.getMeasuredWidth();int height = view.getMeasuredHeight();}}

欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,欢迎转发分享给更多人。欢迎加入程序员小乐技术交流群,在后台回复“加群”或者“学习”即可。

猜你还想看

阿里、腾讯、百度、华为、京东最新面试题汇集

记一次由Redis分布式锁造成的重大事故,避免以后踩坑!

审阅“史上“最烂的代码

使用IntelliJ IDEA查看类图,内容极度舒适

嘿,你在看吗

万字长文!面试官问你:自定义View跟绘制流程懂吗?帮你搞定面试官相关推荐

  1. concurrent 底层_万字长文!从底层开始带你了解并发编程,彻底帮你搞懂Java锁!

    线程是否要锁住同步资源 锁住 悲观锁 不锁住 乐观锁 锁住同步资源失败 线程是否要阻塞 阻塞 不阻塞自旋锁,适应性自旋锁 多个线程竞争同步资源的流程细节有没有区别 不锁住资源,多个线程只有一个能修改资 ...

  2. java底层编程_万字长文!从底层开始带你了解并发编程,彻底帮你搞懂Java锁!

    线程是否要锁住同步资源锁住 悲观锁 不锁住 乐观锁 锁住同步资源失败 线程是否要阻塞阻塞 不阻塞自旋锁,适应性自旋锁 多个线程竞争同步资源的流程细节有没有区别不锁住资源,多个线程只有一个能修改资源成功 ...

  3. 面试绕不开的 CAP 理论,这篇文章帮你搞定!

    点击关注公众号,实用技术文章及时了解 文章转载于:JAVA日知录   案例背景 CAP 理论是分布式系统中最核心的基础理论,虽然在面试中,面试官不会直白地问你 CAP 理论的原理,但是在面试中遇到的分 ...

  4. 面试第一问:简单做个自我介绍吧,怎么回答才让面试官频频点头?

    面试第一问:简单做个自我介绍吧,怎么回答才让面试官频频点头? 前言 个人的基本信息,扬长避短 突出自己的技能 个人兴趣爱好与结尾 总体案例: 前言 面试问题第一问,99.99999%都是:请先做个自我 ...

  5. 如何在一分钟内搞定面试官

    转载自   如何在一分钟内搞定面试官 很多人的求职面试的过程中都会遇到这个问题:  "请做个自我介绍." 有的人,可以口若悬河.妙语连珠讲3分钟,有的人,可能磕磕巴巴,讲了30秒, ...

  6. android自定义view流程,Android 自定义View--从源码理解View的绘制流程

    前言 在Android的世界里,View扮演着很重要的角色,它是Android世界在视觉上的具体呈现.Android系统本身也提供了很多种原生控件供我们使用,然而在日常的开发中我们很多时候需要去实现一 ...

  7. HenCoder Android 开发进阶:自定义 View 1-5 绘制顺序

    这期是 HenCoder 自定义绘制的第 1-5 期:绘制顺序 之前的内容在这里:  HenCoder Android 开发进阶 自定义 View 1-1 绘制基础  HenCoder Android ...

  8. Android自定义View系列之详解View的绘制流程

    目录 一.开场白 二.View的绘制流程 2.1测量的过程 2.2布局的过程 2.3绘制的过程 一.开场白 开讲之前我们先预设一种自定义ViewGroup的场景:我们知道LinearLayout.Fr ...

  9. 【Android面试】View的绘制流程

    目录 View的绘制流程简介 Activity和window和view 的关系 Activity和Window是什么时候建立联系的呢? ViewRootImpl View的绘制流程总结 View的绘制 ...

  10. Android之View的绘制流程解析

    转载请标明出处:[顾林海的博客] 个人开发的微信小程序,目前功能是书籍推荐,后续会完善一些新功能,希望大家多多支持! ##前言 自定义View在Android中占据着非常重要的地位,因此了解View的 ...

最新文章

  1. android ble 设备扫描程序,Android应用开发Android 7.0 BLE scan 问题:程序无错但扫描不到BLE设备...
  2. python选择语句_3.1Python的判断选择语句
  3. WordPress百度快速提交插件-加速百度爬虫和收录
  4. 4岁的拼多多超越20岁的百度,成为中国第五大互联网公司!
  5. 《代码大全》阅读心得二
  6. 精通开关电源设计第三版pdf_看漫画,学电源(一)丨线性电源与开关电源的构造...
  7. 【包邮免费送】Python 全栈知识图谱
  8. spark structured stream的Update模式
  9. getTime()的00:00:00问题。
  10. vim保存文件”:wq与“:x的区别以及小写:x与大写“:X”的区别
  11. LeetCode 中文刷题手册:LeetCode Cookbook下载
  12. [论文阅读笔记17]MAT: Motion-Aware Multi-Object Tracking
  13. 京东深圳手Q微信事业部测试工程师面试总结
  14. 揭秘!“真假美猴王事件”其实是如来的一次运维事故
  15. hbuilder基座_3图标基座的禅宗
  16. 世界上最伟大的推销员-羊皮卷之八
  17. freerdp 解压安装_linux下使用FreeRDP 连接 Windows 远程桌面
  18. Login with Vimeo in iOS App using oAuth tutorial
  19. dede的文档关键词维护,就是自动加内链锚文本
  20. ubuntu 9.10下的网络电视sopcast的安装及消除播放杂音

热门文章

  1. 微信H5缓存解决方案,适用于uniapp被微信缓存页面导致空白【微信公众号缓存】
  2. Linux服务器命令
  3. 苹果4s变php服务器,苹果4S改装无线充电
  4. 科技狂人埃隆·马斯克
  5. 综述类论文怎么写引言和结语?
  6. Linux如何不格式化挂载硬盘,linux下格式化硬盘与挂载硬盘
  7. FFmpeg将音频、无音频的视频合并为视频
  8. Win10 Edge浏览器如何截网页长图
  9. 【JS】388- 深入了解强大的 ES6 「 ... 」 运算符
  10. 2021年全新Java学习路线图,对标阿里P7技术栈