首页 文章 精选 留言 我的

精选列表

搜索[人设自定义],共10000篇文章
优秀的个人博客,低调大师

02.自定义View(RippleTextView可颜色渐变的TextView)

感谢红橙Darren博主 package com.rzm.commonlibrary.views; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.support.annotation.Nullable; import android.text.TextUtils; import android.util.AttributeSet; import android.widget.TextView; /** * Created by renzhenming on 2018/3/22. * 颜色渐变效果的TextView,可用于音乐播放的字幕 */ public class RippleTextView extends TextView{ //最初的画笔 private Paint mOriginPaint; //颜色改变后的画笔 private Paint mChangedPaint; //当前滑动进度 private float mCurrentPercent = 0.6f; //当前设置的文本 private String mText; // 当前朝向 private Direction mDirection = Direction.DIRECTION_LEFT; // 绘制的朝向枚举 public enum Direction { DIRECTION_LEFT, DIRECTION_RIGHT } public RippleTextView(Context context) { this(context,null); } public RippleTextView(Context context, @Nullable AttributeSet attrs) { this(context, attrs,-1); } public RippleTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initPaint(); } private void initPaint() { mOriginPaint = getPaintByColor(Color.BLACK); mChangedPaint = getPaintByColor(Color.RED); } private Paint getPaintByColor(int color) { Paint paint = new Paint(); //抗锯齿 paint.setAntiAlias(true); //防抖动 paint.setDither(true); paint.setColor(color); // 字体的大小设置为TextView的文字大小 paint.setTextSize(getTextSize()); return paint; } /** * 自己绘制,不继承super * @param canvas */ @Override protected void onDraw(Canvas canvas) { //获取textView的文本 mText = getText().toString().trim(); //获取textView宽度 int width = getWidth(); if (!TextUtils.isEmpty(mText)){ //mCurrentPercent进度下,滑动到控件的位置 int position = (int) (width* mCurrentPercent); // 根据不同的朝向去画字体 if (mDirection == Direction.DIRECTION_LEFT) { drawOriginDirectionLeft(canvas, position); drawChangeDirectionLeft(canvas, position); } if (mDirection == Direction.DIRECTION_RIGHT) { drawOriginDirectionRight(canvas, position); drawChangeDirectionRight(canvas, position); } } } /** * 画朝向右边变色字体 */ private void drawChangeDirectionRight(Canvas canvas, int position) { drawText(canvas, mChangedPaint, getWidth() - position, getWidth()); } /** * 画朝向左边默认色字体 */ private void drawOriginDirectionRight(Canvas canvas, int position) { drawText(canvas, mOriginPaint, 0, getWidth() - position); } /** * 画朝向左边变色字体 */ private void drawChangeDirectionLeft(Canvas canvas, int position) { drawText(canvas, mChangedPaint, 0, position); } /** * 画朝向左边默认色字体 */ private void drawOriginDirectionLeft(Canvas canvas, int position) { drawText(canvas, mOriginPaint, position, getWidth()); } /** * 设置当前的进度 * @param currentProgress 当前进度 */ public void setCurrentProgress(float currentProgress) { this.mCurrentPercent = currentProgress; // 重新绘制 invalidate(); } /** * 设置绘制方向,从右到左或者从左到右 * @param direction 绘制方向 */ public void setDirection(Direction direction) { this.mDirection = direction; } /** * 根据滑动位置绘制文本 * @param canvas * @param paint * @param start * @param end * * 基准点是baseline * Ascent是baseline之上至字符最高处的距离 * Descent是baseline之下至字符最低处的距离 * Top指的是指的是最高字符到baseline的值,即ascent的最大值, * bottom指的是最下字符到baseline的值,即descent的最大值,字能达到的最小值 */ private void drawText(Canvas canvas, Paint paint, int start, int end) { //保存画笔状态 canvas.save(); //截取绘制的内容,该方法用于裁剪画布,也就是设置画布的显示区域 //主要用于部分显示以及对画布中的部分对象进行操作的场合。 canvas.clipRect(start,0,end,getHeight()); //获取文字的范围 Rect bounds = new Rect(); mOriginPaint.getTextBounds(mText,0,mText.length(),bounds); // 获取文字的Metrics 用来计算基线 Paint.FontMetricsInt fontMetrics = mOriginPaint.getFontMetricsInt(); //获取文字的高度 int fontHeight = fontMetrics.bottom - fontMetrics.top; // 计算基线到中心点的位置,fontHeight/2是文字中心点, fontMetrics.bottom是基线到文字最低点的高度, int offY = fontHeight/2 - fontMetrics.bottom; // 计算基线位置,为什么基线的位置这样计算,如果想不通,可以这样,假设,文字在控件正中间,控件高度的一半 //也就是控件顶部到文字中心点的垂直距离,文字高度的一半也就是文字中心点到文字最顶部或者最底部的距离,二者相加 //可以看作是从控件顶部到文字最底部的距离,这个距离是等于控件顶部到基线的距离加上 int baseline = (getMeasuredHeight() + fontHeight) / 2 - offY; //绘制文字 canvas.drawText(mText,getMeasuredWidth()/2-bounds.width()/2,baseline,paint); //恢复画笔状态 canvas.restore(); } }

优秀的个人博客,低调大师

自定义控件View之onMeasure调用时机源码分析

终于建了一个自己个人小站:https://huangtianyu.gitee.io,以后优先更新小站博客,欢迎进站,O(∩_∩)O~~ 先上测试代码: MainActivity.java import android.app.Activity; import android.os.Bundle; import android.util.Log; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.e("hty", "before setContextView"); setContentView(R.layout.activity_main); Log.e("hty", "after setContextView"); } @Override protected void onResume() { super.onResume(); Log.e("hty", "onResume"); } @Override protected void onDestroy() { super.onDestroy(); Log.e("hty", "onDestroy"); } }MyView.java import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.AttributeSet; import android.util.Log; import android.view.View; public class MyView extends View { Paint paint; public MyView(Context context) { this(context, null); } public MyView(Context context, AttributeSet attrs) { super(context, attrs); Log.e("hty","view constructor"); paint = new Paint(); paint.setColor(Color.RED); paint.setTextSize(20); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); Log.e("hty","view onMeasure"); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); Log.e("hty","view onLayout"); } String str = "这里是测试"; @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); Log.e("hty","view onDraw"); canvas.drawText(str, getWidth()/2-paint.measureText(str)/2,getHeight()/2, paint); } } activity_main.xml <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.zqc.mytest.MainActivity"> <com.zqc.mytest.MyView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" /> </RelativeLayout> 正常运行后,查看对应的Log: 从Log输出可以看出在一个View的绘制过程中,onMeasure是被多次调用了的。下面通过源码来一步步分析onMeasure(int widthMeasureSpec, int heightMeasureSpec)函数,尤其是传过来的两个参数到底是从哪里来的。 首先看下MainActivity里面的setContentView,进入该函数后,其对应的代码如下: Activity.java public void setContentView(@LayoutRes int layoutResID) { getWindow().setContentView(layoutResID); initWindowDecorActionBar(); } 即调用了getWindow()的setContentView方法,查看getWindow方法,其返还的是类Window的一个实例mWindow,该类是一个抽象类,其具体实现类是PhoneWindow,即调用的是PhoneWindow的setContentView方法,查看相应的代码如下: PhoneWindow.java @Override public void setContentView(int layoutResID) { // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window // decor, when theme attributes and the like are crystalized. Do not check the feature // before this happens. if (mContentParent == null) { installDecor(); } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); } if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, getContext()); transitionTo(newScene); } else { mLayoutInflater.inflate(layoutResID, mContentParent); } mContentParent.requestApplyInsets(); final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } } 该方法首先判断mContentParent是否为空,不为空则调用installDecor()方法来初始化mContentParent,查看具体的代码: PhoneWindow.java private void installDecor() { if (mDecor == null) { mDecor = generateDecor();//这里生成了mDecor,它是所有应用窗口的根View 。 mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); mDecor.setIsRootNamespace(true); if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) { mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); } } if (mContentParent == null) { mContentParent = generateLayout(mDecor);//这里就生成了mContentParent,这个generateLayout会根据设定的style来布局显示的界面 // Set up decor part of UI to ignore fitsSystemWindows if appropriate. mDecor.makeOptionalFitsSystemWindows(); .... } } 其中generateDecor方法就直接返回一个DecorView,代码如下: PhoneWindow.java protected DecorView generateDecor() { return new DecorView(getContext(), -1); } 而generateLayout(mDecor)方法会根据程序Activity设定的style来布局显示的界面,其代码如下: PhoneWindow.java protected ViewGroup generateLayout(DecorView decor) { // Apply data from current theme. TypedArray a = getWindowStyle();//获取窗口的style 。。。。 if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) { requestFeature(FEATURE_NO_TITLE);//看到没,你在xml里面设置的FEATURE_NO_TITLE,在这里生效了 } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) { // Don't allow an action bar if there is no title. requestFeature(FEATURE_ACTION_BAR); } 。。。。 final Context context = getContext(); 。。。。 WindowManager.LayoutParams params = getAttributes(); if (!hasSoftInputMode()) { params.softInputMode = a.getInt( R.styleable.Window_windowSoftInputMode, params.softInputMode); } if (a.getBoolean(R.styleable.Window_backgroundDimEnabled, mIsFloating)) { /* All dialogs should have the window dimmed */ if ((getForcedWindowFlags()&WindowManager.LayoutParams.FLAG_DIM_BEHIND) == 0) { params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND; } if (!haveDimAmount()) { params.dimAmount = a.getFloat( android.R.styleable.Window_backgroundDimAmount, 0.5f); } } 。。。。 int layoutResource; int features = getLocalFeatures(); 。。。。 View in = mLayoutInflater.inflate(layoutResource, null);//这里把给定的布局加载出来,然后加到decor中 decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); mContentRoot = (ViewGroup) in; ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);//看到没这个ID_ANDROID_CONTENT,也就是一个窗口的根布局 if (contentParent == null) { throw new RuntimeException("Window couldn't find content container view"); } 。。。。 mDecor.finishChanging(); return contentParent; } 通过一张图来分析下一个窗口的布局具体是怎样的。 图上标的很详细,在最外层是一个FramLayout,其实也就是DecorView,是所有窗口的根布局,在该根布局下有一个(0)LinearLayout和一个(1)View,这个(1)View就是状态栏,(0)LinearLayout里面有个FrameLayout,在里面的多个View有固定的id,在图中已经标明,所有在一个Activity通过findViewById获取的ID_ANDROID_CONTENT就是(0)FrameLayout->(0)LinearLayout->(0)FrameLayout->(1)FrameLayout对应的View。要知道onMeasure两个参数到底是从哪里来的,还得再找下View是如何绘制的,上一篇文章有分析。View的绘制从ViewRootImpl的performTraversals()函数开始,下面进入该方法中具体分析下。 ViewRootImpl.java private void performTraversals() { // cache mView since it is used so much below... final View host = mView; 。。。。 mIsInTraversal = true; mWillDrawSoon = true; boolean windowSizeMayChange = false; boolean newSurface = false; boolean surfaceChanged = false; WindowManager.LayoutParams lp = mWindowAttributes; int desiredWindowWidth; int desiredWindowHeight; final int viewVisibility = getHostVisibility(); boolean viewVisibilityChanged = mViewVisibility != viewVisibility || mNewSurfaceNeeded; WindowManager.LayoutParams params = null; if (mWindowAttributesChanged) { mWindowAttributesChanged = false; surfaceChanged = true; params = lp; } 。。。。 Rect frame = mWinFrame; if (mFirst) { mFullRedrawNeeded = true; mLayoutRequested = true; if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL || lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) { // NOTE -- system code, won't try to do compat mode. Point size = new Point(); mDisplay.getRealSize(size); desiredWindowWidth = size.x; desiredWindowHeight = size.y; } else { DisplayMetrics packageMetrics = mView.getContext().getResources().getDisplayMetrics(); desiredWindowWidth = packageMetrics.widthPixels; desiredWindowHeight = packageMetrics.heightPixels; } 。。。。 } else { desiredWindowWidth = frame.width(); desiredWindowHeight = frame.height(); if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) { if (DEBUG_ORIENTATION) Log.v(TAG, "View " + host + " resized to: " + frame); mFullRedrawNeeded = true; mLayoutRequested = true; windowSizeMayChange = true; } } if (viewVisibilityChanged) { mAttachInfo.mWindowVisibility = viewVisibility; host.dispatchWindowVisibilityChanged(viewVisibility); if (viewVisibility != View.VISIBLE || mNewSurfaceNeeded) { destroyHardwareResources(); } if (viewVisibility == View.GONE) { // After making a window gone, we will count it as being // shown for the first time the next time it gets focus. mHasHadWindowFocus = false; } } 。。。。 boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw); if (layoutRequested) { final Resources res = mView.getContext().getResources(); if (mFirst) { // make sure touch mode code executes by setting cached value // to opposite of the added touch mode. mAttachInfo.mInTouchMode = !mAddedTouchMode; ensureTouchModeLocally(mAddedTouchMode); } else { 。。。。 if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) { windowSizeMayChange = true; if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL || lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) { // NOTE -- system code, won't try to do compat mode. Point size = new Point(); mDisplay.getRealSize(size); desiredWindowWidth = size.x; desiredWindowHeight = size.y; } else { DisplayMetrics packageMetrics = res.getDisplayMetrics(); desiredWindowWidth = packageMetrics.widthPixels; desiredWindowHeight = packageMetrics.heightPixels; } } } // Ask host how big it wants to be windowSizeMayChange |= measureHierarchy(host, lp, res, desiredWindowWidth, desiredWindowHeight); } 。。。。 if (!mStopped || mReportNextDraw) { boolean focusChangedDueToTouchMode = ensureTouchModeLocally( (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0); if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight() || contentInsetsChanged) { int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);//获取 int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); if (DEBUG_LAYOUT) Log.v(TAG, "Ooops, something changed! mWidth=" + mWidth + " measuredWidth=" + host.getMeasuredWidth() + " mHeight=" + mHeight + " measuredHeight=" + host.getMeasuredHeight() + " coveredInsetsChanged=" + contentInsetsChanged); // Ask host how big it wants to be performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//看这里,看这里 // Implementation of weights from WindowManager.LayoutParams // We just grow the dimensions as needed and re-measure if // needs be int width = host.getMeasuredWidth(); int height = host.getMeasuredHeight(); boolean measureAgain = false; if (lp.horizontalWeight > 0.0f) { width += (int) ((mWidth - width) * lp.horizontalWeight); childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY); measureAgain = true; } if (lp.verticalWeight > 0.0f) { height += (int) ((mHeight - height) * lp.verticalWeight); childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); measureAgain = true; } if (measureAgain) { if (DEBUG_LAYOUT) Log.v(TAG, "And hey let's measure once more: width=" + width + " height=" + height); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//看这里,看这里 } layoutRequested = true; } } } 。。。。 mIsInTraversal = false; } private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure"); try { mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } } 看performTraversals方法中调用的performMeasure的地方,performMeasure即调用了View的measure方法,而measure方法会去调用onMeasure方法。 看下如下两行代码 int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);//获取 int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); 在这两行代码中获取了child的宽高,使用的方法是getRootMeasureSpec,其中参数lp.width是传入的MATCH_PARENT或者WRAP_CONTENT,mWidth是窗口期望的大小,getRootMeasureSpec代码如下: ViewRootImpl.java private static int getRootMeasureSpec(int windowSize, int rootDimension) { int measureSpec; switch (rootDimension) { case ViewGroup.LayoutParams.MATCH_PARENT: // Window can't resize. Force root view to be windowSize. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); break; case ViewGroup.LayoutParams.WRAP_CONTENT: // Window can resize. Set max size for root view. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST); break; default: // Window wants to be an exact size. Force root view to be that size. measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY); break; } return measureSpec; } 这里应该很好理解了,其中调用了MeasureSpec类中的方法,关于MeasureSpec类网上资料很多,该类中用一个int值的两部分分别表示Mode和具体的尺寸。其中最高两位表示 Mode,而最低的30位表示具体的尺寸值,这里计算完之后就进入了View的measure函数中,代码如下: View.java public final void measure(int widthMeasureSpec, int heightMeasureSpec) { boolean optical = isLayoutModeOptical(this); if (optical != isLayoutModeOptical(mParent)) { Insets insets = getOpticalInsets(); int oWidth = insets.left + insets.right; int oHeight = insets.top + insets.bottom; widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth); heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight); } // Suppress sign extension for the low bytes long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL; if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2); if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT || widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec) { // first clears the measured dimension flag mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET; resolveRtlPropertiesIfNeeded(); int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 : mMeasureCache.indexOfKey(key); if (cacheIndex < 0 || sIgnoreMeasureCache) { // measure ourselves, this should set the measured dimension flag back onMeasure(widthMeasureSpec, heightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } else { long value = mMeasureCache.valueAt(cacheIndex); // Casting a long to int drops the high 32 bits, no mask needed setMeasuredDimensionRaw((int) (value >> 32), (int) value); mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } // flag not set, setMeasuredDimension() was not invoked, we raise // an exception to warn the developer if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) { throw new IllegalStateException("View with id " + getId() + ": " + getClass().getName() + "#onMeasure() did not set the" + " measured dimension by calling" + " setMeasuredDimension()"); } mPrivateFlags |= PFLAG_LAYOUT_REQUIRED; } mOldWidthMeasureSpec = widthMeasureSpec; mOldHeightMeasureSpec = heightMeasureSpec; mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 | (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension } 该方法是final的,因而不能被继承,但是里面提供了onMeasure回调,这样子类就可以直接继承onMeasure函数来实现相应的操作。这个View类型的,但是还有一种是ViewGroup类型,也就是容器类型的控件,在具体容器类型的控件里面可以通过重写onMeasure来实现,比如FrameLayout中的onMeasure函数如下: FrameLayout.java protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int count = getChildCount(); final boolean measureMatchParentChildren = MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY || MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY; mMatchParentChildren.clear(); int maxHeight = 0; int maxWidth = 0; int childState = 0; for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (mMeasureAllChildren || child.getVisibility() != GONE) { measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); maxWidth = Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); childState = combineMeasuredStates(childState, child.getMeasuredState()); if (measureMatchParentChildren) { if (lp.width == LayoutParams.MATCH_PARENT || lp.height == LayoutParams.MATCH_PARENT) { mMatchParentChildren.add(child); } } } } // Account for padding too maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground(); maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground(); // Check against our minimum height and width maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); // Check against our foreground's minimum height and width final Drawable drawable = getForeground(); if (drawable != null) { maxHeight = Math.max(maxHeight, drawable.getMinimumHeight()); maxWidth = Math.max(maxWidth, drawable.getMinimumWidth()); } setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT)); count = mMatchParentChildren.size(); if (count > 1) { for (int i = 0; i < count; i++) { final View child = mMatchParentChildren.get(i); final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); final int childWidthMeasureSpec; if (lp.width == LayoutParams.MATCH_PARENT) { final int width = Math.max(0, getMeasuredWidth() - getPaddingLeftWithForeground() - getPaddingRightWithForeground() - lp.leftMargin - lp.rightMargin); 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) { final int height = Math.max(0, getMeasuredHeight() - getPaddingTopWithForeground() - getPaddingBottomWithForeground() - lp.topMargin - lp.bottomMargin); childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( height, MeasureSpec.EXACTLY); } else { childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, getPaddingTopWithForeground() + getPaddingBottomWithForeground() + lp.topMargin + lp.bottomMargin, lp.height); } child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } } }大概也就是回调本容器里面的子View的measure函数实现尺寸计算。这里通过方法ViewGroup类中的getChildMeasureSpec()来获取子类期望自己获取的宽高大小。其代码是 ViewGroup.java 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) { // Parent has imposed an exact size on us case MeasureSpec.EXACTLY: if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size. So be it. resultSize = size; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent has imposed a maximum size on us case MeasureSpec.AT_MOST: if (childDimension >= 0) { // Child wants a specific size... so be it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size, but our size is not fixed. // Constrain child to not be bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent asked to see how big we want to be case MeasureSpec.UNSPECIFIED: if (childDimension >= 0) { // Child wants a specific size... let him have it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size... find out how big it should // be resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size.... find out how // big it should be resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } break; } return MeasureSpec.makeMeasureSpec(resultSize, resultMode); } 在重写onMeasure方法时一定要调用setMeasuredDimension,该方法会将mPrivateFlags经过或使得View知道已经经过了measure这个步骤了。代码如下: View.java 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); } private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) { mMeasuredWidth = measuredWidth; mMeasuredHeight = measuredHeight; mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET; } 至此分析结束,所以说一个View的大小是由自己和父类两者共同决定的。

优秀的个人博客,低调大师

在CentOS 7上利用systemctl添加自定义系统服务

CentOS7继承了RHEL 7的新的特性,例如强大的systemctl,而systemctl的使用也使得以往系统服务的/etc/init.d的启动脚本的方式就此改变,也大幅提高了系统服务的运行效率。但服务的配置和以往也发生了极大的不同,说实在的,变的简单而易用了许多。 下面我们以利用forever来实现Node.js项目自启动为例,初探CentOS 7的systemctl。 前提:Node.js环境已配置成功,forever包安装成功,有一个能跑的Node.js程序。 CentOS 7的服务systemctl脚本存放在:/usr/lib/systemd/,有系统(system)和用户(user)之分,像需要开机不登陆就能运行的程序,还是存在系统服务里吧,即:/usr/lib/systemd/system目录下 每一个服务以.service结尾,一般会分为3部分:[Unit]、[Service]和[Install],我写的这个服务用于开机运行Node.js项目,具体内容如下: [plain]view plain copy print? <spanstyle="font-family:CourierNew;font-size:12px;">[Unit] Description=xiyoulibapi After=network.targetremote-fs.targetnss-lookup.target [Service] Type=forking PIDFile=/node.js/pid ExecStart=/usr/local/bin/foreverstart/node.js/xiyoulib/bin/www ExecReload=/usr/local/bin/foreverrestart/node.js/xiyoulib/bin/www ExecStop=/usr/local/bin/foreverstop/node.js/xiyoulib/bin/www PrivateTmp=true [Install] WantedBy=multi-user.target</span> [Unit]部分主要是对这个服务的说明,内容包括Description和After,Description用于描述服务,After用于描述服务类别 [Service]部分是服务的关键,是服务的一些具体运行参数的设置,这里Type=forking是后台运行的形式,PIDFile为存放PID的文件路径,ExecStart为服务的具体运行命令,ExecReload为重启命令,ExecStop为停止命令,PrivateTmp=True表示给服务分配独立的临时空间,注意:[Service]部分的启动、重启、停止命令全部要求使用绝对路径,使用相对路径则会报错! [Install]部分是服务安装的相关设置,可设置为多用户的 服务脚本按照上面编写完成后,以754的权限保存在/usr/lib/systemd/system目录下,这时就可以利用systemctl进行配置了 首先,使用systemctl start [服务名(也是文件名)]可测试服务是否可以成功运行,如果不能运行则可以使用systemctl status [服务名(也是文件名)]查看错误信息和其他服务信息,然后根据报错进行修改,直到可以start,如果不放心还可以测试restart和stop命令。 接着,只要使用systemctl enable xxxxx就可以将所编写的服务添加至开机启动即可。 我的脚本编写方法参照了nginx的编写方法,也可以根据其他功能类似的程序。 这样看来,虽然systemctl比较陌生,但是其实比init.d那种方式简单不少,而且使用简单,systemctl能简化的操作还有很多,现在也有不少的资料,看来RHEL/CentOS比其他的Linux发行版还是比较先进的,此次更新也终于舍弃了Linux 2.6内核,无论是速度还是稳定性都提升不少。 tomcat服务: [plain]view plaincopy vim/usr/lib/systemd/system/tomcat.service [plain]view plaincopy [Unit] Description=tomcatapi After=network.target [Service] Type=forking PIDFile=/usr/local/tomcat/tomcat.pid ExecStart=/usr/local/tomcat/bin/startup.sh ExecReload= ExecStop=/usr/local/tomcat/bin/shutdown.sh PrivateTmp=true [Install] WantedBy=multi-user.target mysql服务: [Unit] Description=mysql Server After=Network.target After=syslog.target [Install] WantedBy=multi-user.target [Service] User=mysql Group=mysql Type=forking # Execute pre and post scripts as root PermissionsStartOnly=true # Start main service ExecStart=/usr/sbin/mysqld --daemonize ExecStop=kill -s -QUIT $MAINPID Restart=on-failure RestartPreventExitStatus=1 PrivateTmp=false [plain]view plain copy print? [Unit] Description=TheApacheHTTPServer After=network.targetremote-fs.targetnss-lookup.target [Service] Type=notify EnvironmentFile=/etc/sysconfig/httpd ExecStart=/usr/sbin/httpd$OPTIONS-DFOREGROUND ExecReload=/usr/sbin/httpd$OPTIONS-kgraceful ExecStop=/bin/kill-WINCH${MAINPID} #Wewantsystemdtogivehttpdsometimetofinishgracefully,butstillwant #ittokillhttpdafterTimeoutStopSecifsomethingwentwrongduringthe #gracefulstop.Normally,SystemdsendsSIGTERMsignalrightafterthe #ExecStop,whichwouldkillhttpd.WearesendinguselessSIGCONTheretogive #httpdtimetofinish. KillSignal=SIGCONT PrivateTmp=true [Install] WantedBy=multi-user.target [plain]view plain copy print? <precode_snippet_id="2433906"snippet_file_name="blog_20170604_2_4662824"></pre> <pstyle="font-size:14px;line-height:22.4px;text-align:justify">参考:<br> http://www.linuxidc.com/Linux/2014-07/104487.htm<br> http://blog.csdn.net/gbenson/article/details/51083817<br> http://www.07net01.com/2016/05/1514170.html<br> </p> <pstyle="font-size:14px;line-height:22.4px;text-align:justify"></p> <pre></pre> <pre></pre>

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册