详解Android布局加载流程源码

一.首先看布局层次 看这么几张图

我们会发现DecorView里面包裹的内容可能会随着不同的情况而变化,但是在Decor之前的层次关系都是固定的。即Activity包裹PhoneWindow,PhoneWindow包裹DecorView。接下来我们首先看一下三者分别是如何创建的。

二.Activity是如何创建的

首先看到入口类ActivityThreadperformLaunchActivity方法:

 private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
		...
        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess();
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } catch (Exception e) {
            ...
        }

有句特别关键的代码,即

activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);

所以activityInstrumentation类的newActivity方法创建的,追踪过去,源码如下

public Activity newActivity(ClassLoader cl, String className,
            Intent intent)
            throws InstantiationException, IllegalAccessException,
            ClassNotFoundException {
        String pkg = intent != null && intent.getComponent() != null
                ? intent.getComponent().getPackageName() : null;
        return getFactory(pkg).instantiateActivity(cl, className, intent);
    }

追踪源码,可知getFactory方法返回一个AppComponentFactory对象,然后调用AppComponentFactoryinstantiateActivity方法,继续追踪

public @NonNull Activity instantiateActivity(@NonNull ClassLoader cl, @NonNull String className,
            @Nullable Intent intent)
            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        return (Activity) cl.loadClass(className).newInstance();
    }

到这里就结束了,我们可以发现Activity是通过反射创建的。

三.PhoneWindow的创建

我们还是回到ActivityThreadperformLaunchActivity方法,在刚刚展示的那一段的下面有如下部分代码

		Window window = null;
        if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
            window = r.mPendingRemoveWindow;
            r.mPendingRemoveWindow = null;
            r.mPendingRemoveWindowManager = null;
        }

        // Activity resources must be initialized with the same loaders as the
        // application context.
        appContext.getResources().addLoaders(
                app.getResources().getLoaders().toArray(new ResourcesLoader[0]));

        appContext.setOuterContext(activity);
        activity.attach(appContext, this, getInstrumentation(), r.token,
                r.ident, app, r.intent, r.activityInfo, title, r.parent,
                r.embeddedID, r.lastNonConfigurationInstances, config,
                r.referrer, r.voiceInteractor, window, r.configCallback,
                r.assistToken);

activity.attach这个方法中,传入了一个Window对象,追踪这个attach方法,里面有一句关键代码

mWindow = new PhoneWindow(this, window, activityConfigCallback);

此时就创建了PhoneWindow。所以我们可以知道,在Activity创建完之后,会为当前的Activity创建一个PhoneWindow对象。

四.DecorView的创建

DecorView的创建就不是performLaunchActivity方法里面了,这次我们从ActivitysetContentView的源码开始分析。下面的ActivitysetContentView方法的内容。

public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

我们发现,ActivitysetContentView实际是调用了PhoneWindowsetContentView方法,跟踪源码。我们会首先进入Window抽象类,然后我们找其子类PhoneWindow,在里面找到setContentView方法

public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }
		...

mContentParentnull时,会调用installDecor方法,追踪进入

private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);
        }
        ...

它调用了generateDecor方法,追踪进入

protected DecorView generateDecor(int featureId) {
        // System process doesn't have application context and in that case we need to directly use
        // the context we have. Otherwise we want the application context, so we don't cling to the
        // activity.
        Context context;
        if (mUseDecorContext) {
            Context applicationContext = getContext().getApplicationContext();
            if (applicationContext == null) {
                context = getContext();
            } else {
                context = new DecorContext(applicationContext, this);
                if (mTheme != -1) {
                    context.setTheme(mTheme);
                }
            }
        } else {
            context = getContext();
        }
        return new DecorView(context, featureId, this, getAttributes());
    }

会发现generateDecor方法会创建一个DecorView对象,并且作为返回值返回。再追踪DecorView

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks

会发现DecorView其实是一个FrameLayout 。到这就介绍完DecorView是如何创建的了

五.布局加载流程

我们回到PhoneWindowinstallDecor方法,再刚刚看的部分的下面,有(2692行)

if (mContentParent == null) {mContentParent = generateLayout(mDecor);

追踪进入

 protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.

        TypedArray a = getWindowStyle();

        if (false) {
            System.out.println("From style:");
            String s = "Attrs:";
            for (int i = 0; i < R.styleable.Window.length; i++) {
                s = s + " " + Integer.toHexString(R.styleable.Window[i]) + "="
                        + a.getString(i);
            }
            System.out.println(s);
        }

        mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
        int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
                & (~getForcedWindowFlags());
        if (mIsFloating) {
            setLayout(WRAP_CONTENT, WRAP_CONTENT);
            setFlags(0, flagsToUpdate);
        } else {
            setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
            getAttributes().setFitInsetsSides(0);
            getAttributes().setFitInsetsTypes(0);
        }

        if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
            requestFeature(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);
        }

        if (a.getBoolean(R.styleable.Window_windowActionBarOverlay, false)) {
            requestFeature(FEATURE_ACTION_BAR_OVERLAY);
        }

        if (a.getBoolean(R.styleable.Window_windowActionModeOverlay, false)) {
            requestFeature(FEATURE_ACTION_MODE_OVERLAY);
        }

        if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
            setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
        }

        if (a.getBoolean(R.styleable.Window_windowTranslucentStatus,
                false)) {
            setFlags(FLAG_TRANSLUCENT_STATUS, FLAG_TRANSLUCENT_STATUS
                    & (~getForcedWindowFlags()));
        }

        if (a.getBoolean(R.styleable.Window_windowTranslucentNavigation,
                false)) {
            setFlags(FLAG_TRANSLUCENT_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION
                    & (~getForcedWindowFlags()));
        }

        if (a.getBoolean(R.styleable.Window_windowShowWallpaper, false)) {
            setFlags(FLAG_SHOW_WALLPAPER, FLAG_SHOW_WALLPAPER&(~getForcedWindowFlags()));
        }

        if (a.getBoolean(R.styleable.Window_windowEnableSplitTouch,
                getContext().getApplicationInfo().targetSdkVersion
                        >= android.os.Build.VERSION_CODES.HONEYCOMB)) {
            setFlags(FLAG_SPLIT_TOUCH, FLAG_SPLIT_TOUCH&(~getForcedWindowFlags()));
        }

        a.getValue(R.styleable.Window_windowMinWidthMajor, mMinWidthMajor);
        a.getValue(R.styleable.Window_windowMinWidthMinor, mMinWidthMinor);
        if (DEBUG) Log.d(TAG, "Min width minor: " + mMinWidthMinor.coerceToString()
                + ", major: " + mMinWidthMajor.coerceToString());
        if (a.hasValue(R.styleable.Window_windowFixedWidthMajor)) {
            if (mFixedWidthMajor == null) mFixedWidthMajor = new TypedValue();
            a.getValue(R.styleable.Window_windowFixedWidthMajor,
                    mFixedWidthMajor);
        }
        if (a.hasValue(R.styleable.Window_windowFixedWidthMinor)) {
            if (mFixedWidthMinor == null) mFixedWidthMinor = new TypedValue();
            a.getValue(R.styleable.Window_windowFixedWidthMinor,
                    mFixedWidthMinor);
        }
        if (a.hasValue(R.styleable.Window_windowFixedHeightMajor)) {
            if (mFixedHeightMajor == null) mFixedHeightMajor = new TypedValue();
            a.getValue(R.styleable.Window_windowFixedHeightMajor,
                    mFixedHeightMajor);
        }
        if (a.hasValue(R.styleable.Window_windowFixedHeightMinor)) {
            if (mFixedHeightMinor == null) mFixedHeightMinor = new TypedValue();
            a.getValue(R.styleable.Window_windowFixedHeightMinor,
                    mFixedHeightMinor);
        }
        if (a.getBoolean(R.styleable.Window_windowContentTransitions, false)) {
            requestFeature(FEATURE_CONTENT_TRANSITIONS);
        }
        if (a.getBoolean(R.styleable.Window_windowActivityTransitions, false)) {
            requestFeature(FEATURE_ACTIVITY_TRANSITIONS);
        }

        mIsTranslucent = a.getBoolean(R.styleable.Window_windowIsTranslucent, false);

        final Context context = getContext();
        final int targetSdk = context.getApplicationInfo().targetSdkVersion;
        final boolean targetPreL = targetSdk < android.os.Build.VERSION_CODES.LOLLIPOP;
        final boolean targetPreQ = targetSdk < Build.VERSION_CODES.Q;

        if (!mForcedStatusBarColor) {
            mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, 0xFF000000);
        }
        if (!mForcedNavigationBarColor) {
            mNavigationBarColor = a.getColor(R.styleable.Window_navigationBarColor, 0xFF000000);
            mNavigationBarDividerColor = a.getColor(R.styleable.Window_navigationBarDividerColor,
                    0x00000000);
        }
        if (!targetPreQ) {
            mEnsureStatusBarContrastWhenTransparent = a.getBoolean(
                    R.styleable.Window_enforceStatusBarContrast, false);
            mEnsureNavigationBarContrastWhenTransparent = a.getBoolean(
                    R.styleable.Window_enforceNavigationBarContrast, true);
        }

        WindowManager.LayoutParams params = getAttributes();

        // Non-floating windows on high end devices must put up decor beneath the system bars and
        // therefore must know about visibility changes of those.
        if (!mIsFloating) {
            if (!targetPreL && a.getBoolean(
                    R.styleable.Window_windowDrawsSystemBarBackgrounds,
                    false)) {
                setFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
                        FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS & ~getForcedWindowFlags());
            }
            if (mDecor.mForceWindowDrawsBarBackgrounds) {
                params.privateFlags |= PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
            }
        }
        if (a.getBoolean(R.styleable.Window_windowLightStatusBar, false)) {
            decor.setSystemUiVisibility(
                    decor.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
        }
        if (a.getBoolean(R.styleable.Window_windowLightNavigationBar, false)) {
            decor.setSystemUiVisibility(
                    decor.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
        }
        if (a.hasValue(R.styleable.Window_windowLayoutInDisplayCutoutMode)) {
            int mode = a.getInt(R.styleable.Window_windowLayoutInDisplayCutoutMode, -1);
            if (mode < LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
                    || mode > LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) {
                throw new UnsupportedOperationException("Unknown windowLayoutInDisplayCutoutMode: "
                        + a.getString(R.styleable.Window_windowLayoutInDisplayCutoutMode));
            }
            params.layoutInDisplayCutoutMode = mode;
        }

        if (mAlwaysReadCloseOnTouchAttr || getContext().getApplicationInfo().targetSdkVersion
                >= android.os.Build.VERSION_CODES.HONEYCOMB) {
            if (a.getBoolean(
                    R.styleable.Window_windowCloseOnTouchOutside,
                    false)) {
                setCloseOnTouchOutsideIfNotSet(true);
            }
        }

        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);
            }
        }

        if (params.windowAnimations == 0) {
            params.windowAnimations = a.getResourceId(
                    R.styleable.Window_windowAnimationStyle, 0);
        }

        // The rest are only done if this window is not embedded; otherwise,
        // the values are inherited from our container.
        if (getContainer() == null) {
            if (mBackgroundDrawable == null) {

                if (mFrameResource == 0) {
                    mFrameResource = a.getResourceId(R.styleable.Window_windowFrame, 0);
                }

                if (a.hasValue(R.styleable.Window_windowBackground)) {
                    mBackgroundDrawable = a.getDrawable(R.styleable.Window_windowBackground);
                }
            }
            if (a.hasValue(R.styleable.Window_windowBackgroundFallback)) {
                mBackgroundFallbackDrawable =
                        a.getDrawable(R.styleable.Window_windowBackgroundFallback);
            }
            if (mLoadElevation) {
                mElevation = a.getDimension(R.styleable.Window_windowElevation, 0);
            }
            mClipToOutline = a.getBoolean(R.styleable.Window_windowClipToOutline, false);
            mTextColor = a.getColor(R.styleable.Window_textColor, Color.TRANSPARENT);
        }

        // Inflate the window decor.

        int layoutResource;
        int features = getLocalFeatures();
        // System.out.println("Features: 0x" + Integer.toHexString(features));
        if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleIconsDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_title_icons;
            }
            // XXX Remove this once action bar supports these features.
            removeFeature(FEATURE_ACTION_BAR);
            // System.out.println("Title Icons!");
        } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
                && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
            // Special case for a window with only a progress bar (and title).
            // XXX Need to have a no-title version of embedded windows.
            layoutResource = R.layout.screen_progress;
            // System.out.println("Progress!");
        } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
            // Special case for a window with a custom title.
            // If the window is floating, we need a dialog layout
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogCustomTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_custom_title;
            }
            // XXX Remove this once action bar supports these features.
            removeFeature(FEATURE_ACTION_BAR);
        } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
            // If no other features and not embedded, only need a title.
            // If the window is floating, we need a dialog layout
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
                layoutResource = a.getResourceId(
                        R.styleable.Window_windowActionBarFullscreenDecorLayout,
                        R.layout.screen_action_bar);
            } else {
                layoutResource = R.layout.screen_title;
            }
            // System.out.println("Title!");
        } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
            layoutResource = R.layout.screen_simple_overlay_action_mode;
        } else {
            // Embedded, so no decoration is needed.
            layoutResource = R.layout.screen_simple;
            // System.out.println("Simple!");
        }

        mDecor.startChanging();
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

        if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
            ProgressBar progress = getCircularProgressBar(false);
            if (progress != null) {
                progress.setIndeterminate(true);
            }
        }

        // Remaining setup -- of background and title -- that only applies
        // to top-level windows.
        if (getContainer() == null) {
            mDecor.setWindowBackground(mBackgroundDrawable);

            final Drawable frame;
            if (mFrameResource != 0) {
                frame = getContext().getDrawable(mFrameResource);
            } else {
                frame = null;
            }
            mDecor.setWindowFrame(frame);

            mDecor.setElevation(mElevation);
            mDecor.setClipToOutline(mClipToOutline);

            if (mTitle != null) {
                setTitle(mTitle);
            }

            if (mTitleColor == 0) {
                mTitleColor = mTextColor;
            }
            setTitleColor(mTitleColor);
        }

        mDecor.finishChanging();

        return contentParent;
    }

分析源码和源码注释可以看出generateLayout方法的核心功能是完成DecorView的布局加载,而且根据不同的主题样式会加载不同的系统默认布局。那么比如有FrameLayout布局,它如何加载到DecorView中呢?
generateLayout方法中,有这么一句

mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

追踪进入

	void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
        if (mBackdropFrameRenderer != null) {
            loadBackgroundDrawablesIfNeeded();
            mBackdropFrameRenderer.onResourcesLoaded(
                    this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
                    mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
                    getCurrentColor(mNavigationColorViewState));
        }

        mDecorCaptionView = createDecorCaptionView(inflater);
        final View root = inflater.inflate(layoutResource, null);
        if (mDecorCaptionView != null) {
            if (mDecorCaptionView.getParent() == null) {
                addView(mDecorCaptionView,
                        new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            }
            mDecorCaptionView.addView(root,
                    new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
        } else {

            // Put it below the color views.
            addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        mContentRoot = (ViewGroup) root;
        initializeElevation();
    }

关键在这里

mDecorCaptionView = createDecorCaptionView(inflater);
final View root = inflater.inflate(layoutResource, null);
if (mDecorCaptionView != null) {
    ...
    mDecorCaptionView.addView(root,
            new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
} 

可以看到layoutResource作为参数,通过inflate方法进行解析加载。然后作为参数,传入addView方法,将布局挂载到上面。至此完成了DecorView的默认布局加载。如果是我们自己编写的布局是如何加载呢?再回到generateLayout

mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

,在此之后,有这么一句

 ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

DecorView执行onResourcesLoaded方法加载完默认布局后,会查找Id为ID_ANDROID_CONTENT的控件(默认布局中的FrameLayout控件)并作为generateLayout方法的返回值,我们回到setContentView方法中,可观察到以下代码:

mLayoutInflater.inflate(layoutResID, mContentParent);

所以,我们自己编写的布局是被加载到DecorView中Id为ID_ANDROID_CONTENT的控件上

六.总结

Activity创建后会创建出一个PhoneWindow对象,当在Activity中调用setContentView时,实际上是调用了PhoneWindowsetContentView方法,此时PhoneWindow会创建根布局DecorView,并根据主题样式,为DecorView加载对应的默认系统布局,在默认的系统布局中包含了一个Id为ID_ANDROID_CONTENT的控件,而我们自己编写的布局就是加载到这个控件中的。

以上就是详解Android布局加载流程源码的详细内容,更多关于Android布局加载流程源码分析的资料请关注我们其它相关文章!

(0)

相关推荐

  • Android实现单行标签流式布局

    近期产品提了有关流式布局的新需求,要求显示字数不定的标签,如果一行显示不完,就只显示一行的内容,而且还在一两个页面采取了多种样式,无语了 自己归类了一下,需求有以下几个区别 1:可选择添加标签与否 2:是否有具体数量限制(比如最多显示3个) 3:是否要靠右对齐 有需求,先找一波现有的工具,看是不是可以直接用 参考Android流式布局实现热门标签效果 FlowLayout原样式: 这个和我们的需求已经比较符合了,但是他并不能控制只显示单行内容 如果要实现这样的布局,官方也提供了Flexbox和F

  • 自己实现Android View布局流程

    相关阅读:尝试自己实现Android View Touch事件分发流程 Android View的布局以ViewRootImpl为起点,开启整个View树的布局过程,而布局过程本身分为测量(measure)和布局(layout)两个部分,以View树本身的层次结构递归布局,确定View在界面中的位置. 下面尝试通过最少的代码,自己实现这套机制,注意下面类均为自定义类,未使用Android 源码中的同名类. MeasureSpec 首先定义MeasureSpec,它是描述父布局对子布局约束的类,在

  • Android课程表界面布局实现代码

    前言 Android课程表布局实现 我是个菜鸟,文章供参考 示例 图1: 图2: 布局分析 该界面主要可分为三部分: 1.显示年份及周数部分 2.显示周一到周日 3.课程显示部分 实现步骤 1.首先整个页面放在一个LinearLayout布局下面,分为上面和下面两个部分,下面一个是显示课程表的详细信息 2.将控件一个TextView用来显示年份,一个View用来当作竖线,再用一个LinearLayout用来显示选择周数 3.使用ScrollView来显示课程表的详细信息 话不多说直接给代码!!!

  • Android自定义流式布局实现淘宝搜索记录

    本文实例为大家分享了Android实现淘宝搜索记录的具体代码,供大家参考,具体内容如下 效果如下: 废话不多说 实现代码: attrs.xml <declare-styleable name="TagFlowLayout"> <!--最大选择数量--> <attr name="max_select" format="integer"/> <!--最大可显示行数--> <attr name=&q

  • Android自定义流式布局的实现示例

    在日常的app使用中,我们会在android 的app中看见 热门标签等自动换行的流式布局,今天,我们就来看看如何自定义一个类似热门标签那样的流式布局.下面我们就来详细介绍流式布局的应用特点以及用的的技术点. 1.流式布局的特点以及应用场景 特点:当上面一行的空间不够容纳新的TextView时候,才开辟下一行的空间. 原理图: 场景:主要用于关键词搜索或者热门标签等场景 2.自定义ViewGroup (1)onMeasure:测量子view的宽高,设置自己的宽和高 (2)onLayout:设置子

  • android实现上下左右滑动界面布局

    本文实例为大家分享了android实现滑动界面布局的具体代码,供大家参考,具体内容如下 1.我使用的是ScrollView嵌套HorizontalScrollView让ScrollView负责上下滑动HorizontalScrollView负责左右滑动 2.以下代码提供了思路和完成手段,请根据具体业务去进行修改,我试过使用recyclerview进行自定义,发现一旦有了复杂业务之后会掉帧卡顿所以使用了这种方法 XML布局 <?xml version="1.0" encoding=

  • Android自定义流式布局/自动换行布局实例

    最近,Google开源了一个流式排版库"FlexboxLayout",功能强大,支持多种排版方式,如各种方向的自动换行等,具体资料各位可搜索学习^_^. 由于我的项目中,只需要从左到右S型的自动换行,需求效果图如下: 使用FlexboxLayout这个框架未免显得有些臃肿,所以自己动手写了一个流式ViewGroup. 安卓中自定义ViewGroup的步骤是: 1. 新建一个类,继承ViewGroup 2. 重写构造方法 3. 重写onMeasure.onLayout方法 onMeasu

  • Android FlowLayout流式布局实现详解

    本文实例为大家分享了Android FlowLayout流式布局的具体代码,供大家参考,具体内容如下 最近使用APP的时候经常看到有 这种流式布局 ,今天我就跟大家一起来动手撸一个这种自定义控件. 首先说一下自定义控件的流程: 自定义控件一般要么继承View要么继承ViewGroup View的自定义流程: 继承一个View-->重写onMeasure方法-->重写onDraw方法-->定义自定义属性-->处理手势操作 ViewGroup的自定义流程: 继承一个ViewGroup-

  • Android自定义ViewGroup实现流式布局

    本文实例为大家分享了Android自定义ViewGroup实现流式布局的具体代码,供大家参考,具体内容如下 1.概述 本篇给大家带来一个实例,FlowLayout,什么是FlowLayout,我们常在App 的搜索界面看到热门搜索词,就是FlowLayout,我们要实现的就是图中的效果,就是根据容器的宽,往容器里面添加元素,如果剩余的控件不足时候,自行添加到下一行,FlowLayout也叫流式布局,在开发中还是挺常用的. 2.对所有的子View进行测量 onMeasure方法的调用次数是不确定的

  • 详解Android布局加载流程源码

    一.首先看布局层次 看这么几张图 我们会发现DecorView里面包裹的内容可能会随着不同的情况而变化,但是在Decor之前的层次关系都是固定的.即Activity包裹PhoneWindow,PhoneWindow包裹DecorView.接下来我们首先看一下三者分别是如何创建的. 二.Activity是如何创建的 首先看到入口类ActivityThread的performLaunchActivity方法: private Activity performLaunchActivity(Activi

  • 详解Android Webview加载网页时发送HTTP头信息

    详解Android Webview加载网页时发送HTTP头信息 当你点击一个超链接进行跳转时,WebView会自动将当前地址作为Referer(引荐)发给服务器,因此很多服务器端程序通过是否包含referer来控制盗链,所以有些时候,直接输入一个网络地址,可能有问题,那么怎么解决盗链控制问题呢,其实在webview加载时加入一个referer就可以了,如何添加呢? 从Android 2.2 (也就是API 8)开始,WebView新增加了一个接口方法,就是为了便于我们加载网页时又想发送其他的HT

  • 详解Android WebView加载html片段

    这里我先简单描述一下需求:服务器返回的是html页面的一部分带有标签的内容. 解决的思路是:将服务器返回的内容片段拼凑成一个完整的页面. 下面直接上核心代码: 这里是WebView一些配置 WebSettings settings = webView.getSettings(); settings.setJavaScriptEnabled(true); settings.setDomStorageEnabled(true); settings.setUseWideViewPort(true);

  • Spring详解四种加载配置项的方法

    目录 1.spring加载yml文件 2.spring 加载 properties 文件 3.spring加载系统磁盘(properties)文件 4.spring加载xml文件 5.Java基于InputStream读取properties配置文件 本文默认 spring 版本是 spring5 1 spring 加载 yml 文件 2 spring 加载 properties 文件 3 spring 加载 系统磁盘 文件 4 spring 加载 xml 文件 5 Java 基于 InputS

  • 详解JS异步加载的三种方式

    一:同步加载 我们平时使用的最多的一种方式. <script src="http://yourdomain.com/script.js"></script> <script src="http://yourdomain.com/script.js"></script> 同步模式,又称阻塞模式,会阻止浏览器的后续处理,停止后续的解析,只有当当前加载完成,才能进行下一步操作.所以默认同步执行才是安全的.但这样如果js中有输

  • 详解Spring ApplicationContext加载过程

    1.找准入口,使用ClassPathXmlApplicationContext的构造方法加载配置文件,用于加载classPath下的配置文件 //第一行,执行完成之后就完成了spring配置文件的加载,刷新spring上下文 ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext( "classpath:spring-mvc.xml"); //获取实例Bean Person person=con

  • 详解Qt如何加载libxl库

    使用工具 1.Qt 5.12.3集成开发环境 2.libxl-3.9.4.3(官方下载地址:https://www.libxl.com/download.html) 提示:以下是本篇文章正文内容,下面案例可供参考 一.如何导入libxl库 由于官方给出的教程是MinGW32导入动态库我这边也照着导入libxl的32位动态库,使用MinGW64开发环境同理,如果qt使用的是mvsc环境的朋友可以不用参考此教程 1.pro文件导入静态链接库 1.把lib32.dll文件路径放入到pro文件中: LI

  • 详解Vue3如何加载动态菜单

    目录 1. 整体思路 2. 实现细节 2.1 加载细节 2.2 getInfo 2.3 generateRoutes 1. 整体思路 首先我们来梳理下整体上的实现思路,首先一点:整体思路和 vhr 一模一样. 考虑到有的小伙伴可能已经忘记 vhr 中前端动态菜单的实现思路了,因此本文再和大家分析一下. 为了确保在所有的 .vue 文件中都能访问到到菜单数据,所以选择将菜单数据存入 vuex 中,vuex 是 vue 中一个存储数据的公共地方,所有的 .vue 文件都可以从 vuex 中读取到数据

  • Spring Security过滤器链加载执行流程源码解析

    目录 Spring Security实现原理 一.Spring Security过滤器链加载 1.注册名为 springSecurityFilterChain的过滤器 2.查看 DelegatingFilterProxy类 3.查看 FilterChainProxy类 3.1 查看 doFilterInternal方法. 3.2 查看 getFilters方法. 4 查看 SecurityFilterChain接口 5 查看 SpringBootWebSecurityConfiguration类

  • SpringBoot整合Spring Security过滤器链加载执行流程源码分析(最新推荐)

    目录 1.引言 2.Spring Security过滤器链加载 2.1.注册名为 springSecurityFilterChain的过滤器 3.查看 DelegatingFilterProxy类 4.查看 FilterChainProxy类 4.1 查看 doFilterInternal方法 4.2 查看 getFilters方法 5 查看 SecurityFilterChain接口 6. 查看 SpringBootWebSecurityConfiguration类 总结: 1.引言 在 Sp

随机推荐