ViewStub原理,解决约束布局( constraintLayout)ViewStub约束失效问题,解决ViewStub约束失效约束不起作用的问题

Android性能优化利器:使用ViewStub优化你的布局 ViewStub的概念,作用,使用方法。android中动态显示隐藏View的两种方式比较。ViewStub的使用举例,注意事项。 阅读详情

解决ViewStub约束布局 约束失效的问题

1.背景

在项目中进行性能优化或包体积控制的时候,就要进行xml或布局优化。
通常我们进行布局优化或xml加载优化的时候,就不得不提到到的几个标签merge、include、ViewStub,这几个标签作用不做赘述,重点讲一下本期的ViewStub;

ViewStub使用场景是,需要复用layout但第一时间用不到,使用ViewStub做延迟加载。其原理特性就是:将目标layout引入后,替换自己,并将自己的属性LayoutParams设置给目标View/layout

ViewStub在使用的时候,有两种方式:

  1. 在在xml中通过layout属性引入目标layout;
  2. 或者在代码中动态使用setLayoutResource注入;

最后在代码调用ViewStub.inflate()或ViewStub.setVisibility(View.VISIBLE)使引入的布局替换ViewStub。

2.问题

上面使用方式没有任何问题,使用起来也非常快捷方便,但问题也随之而来。

  • 一定要有一个layout
    ViewStub引入布局,一定要有一个额外的Layout,即使是Layout里面只有一个View,也需要单独为它提供一个Layout;

  • 它只能引入Layout
    有时候我们在代码中,需要动态的添加一个View,如果ViewGroup比较简单例如线性布局或者FrameLayout还好收,但是复杂的View,相对布局就需要动态设置Rule,而约束布局就相对来说比较复杂了,不是太了解约束布局动态更改约束规则的朋友就头疼了(我是自己研究了一下午才完全搞明白的),需要动态的加好几行代码控制约束,碰到View动态变换位置的场景更是要写好多代码。

于是,我就在想,如何解决这两个问题呢?为什么不能让ViewStub 把这两者结合呢

为什么ViewStub不能用在动态添加View,结合自己的特性,把ViewStub本身的layoutParams设置给动态添加的View呢?

3.寻找可行性

带着上面的问题,我重新看了一下它的源码。

@RemoteView
public final class ViewStub extends View {
    private int mInflatedId;
    private int mLayoutResource;

    private WeakReference<View> mInflatedViewRef;

    private LayoutInflater mInflater;
    private OnInflateListener mInflateListener;

    public ViewStub(Context context) {
        this(context, 0);
    }

    /**
     * Creates a new ViewStub with the specified layout resource.
     *
     * @param context The application's environment.
     * @param layoutResource The reference to a layout resource that will be inflated.
     */
    public ViewStub(Context context, @LayoutRes int layoutResource) {
        this(context, null);

        mLayoutResource = layoutResource;
    }

    public ViewStub(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ViewStub(Context context, AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context);

        final TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.ViewStub, defStyleAttr, defStyleRes);
        saveAttributeDataForStyleable(context, R.styleable.ViewStub, attrs, a, defStyleAttr,
                defStyleRes);

        mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
        mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);
        mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID);
        a.recycle();

        setVisibility(GONE);
        setWillNotDraw(true);
    }

    @IdRes
    public int getInflatedId() {
        return mInflatedId;
    }

    /**
     * Defines the id taken by the inflated view. If the inflated id is
     * {@link View#NO_ID}, the inflated view keeps its original id.
     * 为引入的layout设置一个id
     */
    @android.view.RemotableViewMethod(asyncImpl = "setInflatedIdAsync")
    public void setInflatedId(@IdRes int inflatedId) {
        mInflatedId = inflatedId;
    }

    /** @hide **/
    public Runnable setInflatedIdAsync(@IdRes int inflatedId) {
        mInflatedId = inflatedId;
        return null;
    }

    @LayoutRes
    public int getLayoutResource() {
        return mLayoutResource;
    }

    /**
     * Specifies the layout resource to inflate when this StubbedView becomes visible or invisible
     * or when {@link #inflate()} is invoked. The View created by inflating the layout resource is
     * used to replace this StubbedView in its parent.
     * 注入需要引入的layoutId
     */
    @android.view.RemotableViewMethod(asyncImpl = "setLayoutResourceAsync")
    public void setLayoutResource(@LayoutRes int layoutResource) {
        mLayoutResource = layoutResource;
    }

    /** @hide **/
    public Runnable setLayoutResourceAsync(@LayoutRes int layoutResource) {
        mLayoutResource = layoutResource;
        return null;
    }

    public void setLayoutInflater(LayoutInflater inflater) {
        mInflater = inflater;
    }

    /**
     * Get current {@link LayoutInflater} used in {@link #inflate()}.
     */
    public LayoutInflater getLayoutInflater() {
        return mInflater;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(0, 0);
    }

    @Override
    public void draw(Canvas canvas) {
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
    }

    /**
     * When visibility is set to {@link #VISIBLE} or {@link #INVISIBLE},
     * {@link #inflate()} is invoked and this StubbedView is replaced in its parent
     * by the inflated layout resource. After that calls to this function are passed
     * through to the inflated view.
     * 设置visibility  为VISIBLE 或者INVISIBLE时,会直接引入布局
     */
    @Override
    @android.view.RemotableViewMethod(asyncImpl = "setVisibilityAsync")
    public void setVisibility(int visibility) {
        if (mInflatedViewRef != null) {
            View view = mInflatedViewRef.get();
            if (view != null) {
                view.setVisibility(visibility);
            } else {
                throw new IllegalStateException("setVisibility called on un-referenced view");
            }
        } else {
            super.setVisibility(visibility);
            if (visibility == VISIBLE || visibility == INVISIBLE) {
                inflate();
            }
        }
    }

    /** @hide **/
    public Runnable setVisibilityAsync(int visibility) {
        if (visibility == VISIBLE || visibility == INVISIBLE) {
            ViewGroup parent = (ViewGroup) getParent();
            return new ViewReplaceRunnable(inflateViewNoAdd(parent));
        } else {
            return null;
        }
    }

    private View inflateViewNoAdd(ViewGroup parent) {
        final LayoutInflater factory;
        if (mInflater != null) {
            factory = mInflater;
        } else {
            factory = LayoutInflater.from(mContext);
        }
        final View view = factory.inflate(mLayoutResource, parent, false);

        if (mInflatedId != NO_ID) {
            view.setId(mInflatedId);
        }
        return view;
    }

    private void replaceSelfWithView(View view, ViewGroup parent) {
        final int index = parent.indexOfChild(this);
        //移除自身。
        parent.removeViewInLayout(this);

        final ViewGroup.LayoutParams layoutParams = getLayoutParams();
        if (layoutParams != null) {
        	//把目标view按照自身index添加到parent中,并把自身的layoutParams设置到目标View。
            parent.addView(view, index, layoutParams);
        } else {
            parent.addView(view, index);
        }
    }

    /**
     * 引入布局。核心代码
     */
    public View inflate() {
        final ViewParent viewParent = getParent();

        if (viewParent != null && viewParent instanceof ViewGroup) {
            if (mLayoutResource != 0) {
                final ViewGroup parent = (ViewGroup) viewParent;
                //通过LayoutInflater 引入目标布局,inAttachToParent 为false。
                final View view = inflateViewNoAdd(parent);
                //移除ViewStub自身,并添加目标view到同样的位置,将自身的layoutParams设置给目标View
                replaceSelfWithView(view, parent);

                mInflatedViewRef = new WeakReference<>(view);
                if (mInflateListener != null) {
                    mInflateListener.onInflate(this, view);
                }

                return view;
            } else {
                throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
            }
        } else {
            throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
        }
    }

  
    public void setOnInflateListener(OnInflateListener inflateListener) {
        mInflateListener = inflateListener;
    }

 	/**
 	* 布局引入监听
 	*/
    public static interface OnInflateListener {
   
        void onInflate(ViewStub stub, View inflated);
    }

    /** @hide **/
    public class ViewReplaceRunnable implements Runnable {
        public final View view;

        ViewReplaceRunnable(View view) {
            this.view = view;
        }

        @Override
        public void run() {
            replaceSelfWithView(view, (ViewGroup) getParent());
        }
    }
}

ViewStub代码非常简单,相信各位大佬都能看懂;

  • 构造函数里setVisibility(GONE)和setWillNotDraw(true)方法还有draw和dispatchDraw()方法重写后是空实现我们可知,ViewStub没有被绘制出来,所以不会消耗过多性能。
  • inflate()作用就是引入目标布局但不直接加入parant,而是将自身从parent中移除,然后按照同样的viewIndex把目标View加入到parent中,并把自身的LayoutParams设置给目标View;

原理就是这么简单。

那么,如何解决我们的痛点呢?

4.解决只能引入layout不能动态添加View的问题

重新看一下inflate()方法:

    public View inflate() {
        final ViewParent viewParent = getParent();

        if (viewParent != null && viewParent instanceof ViewGroup) {
            if (mLayoutResource != 0) {
                final ViewGroup parent = (ViewGroup) viewParent;
                //通过LayoutInflater 引入目标布局,inAttachToParent 为false。
                final View view = inflateViewNoAdd(parent);
                //移除ViewStub自身,并添加目标view到同样的位置,将自身的layoutParams设置给目标View
                replaceSelfWithView(view, parent);

                mInflatedViewRef = new WeakReference<>(view);
                if (mInflateListener != null) {
                    mInflateListener.onInflate(this, view);
                }

                return view;
            } else {
                throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
            }
        } else {
            throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
        }
    }

这里替换分为两步:

  • 1.使用LayoutInflate把layout引入;
  • 2.第一步引入的View替换自身;

是不是思路就来了,我完全可以多态出来一个inflate(View view)方法,把我们动态构造的View传进去啊!!

那我是不是可以自定义一个View继承ViewStub呢?😏😏😏

说干就干!

但当我兴致勃勃的去继承ViewStub时候,编译器却报错了,才发现ViewStub有一个注解@RemoteView,它不可以被继承。

那软的不行,来硬的,我直接自己实现一份(其实是就是ctrl+c,ctrl+v😏😏)可以了吧!



import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.IdRes;
import androidx.annotation.LayoutRes;
import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;

import com.avatr.ivi.enoch.R;
import com.avatr.ivi.enochsdk.base.tools.L;

import java.lang.ref.WeakReference;

public class ViewRocket extends View {
    private static final L.Tag TAG = new L.Tag("ViewRocket");
    private Context mContext;
    private WeakReference<View> mInflatedViewRef;
    @LayoutRes
    private int mLayoutResource;
    @IdRes
    private int mInflatedId = NO_ID;
 	private boolean mIsLaunched = false;

    public ViewRocket(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
        mContext = context;
    }

    public ViewRocket(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewRocket);
        mLayoutResource = a.getResourceId(R.styleable.ViewRocket_layout, 0);
        mInflatedId = a.getResourceId(R.styleable.ViewRocket_inflatedId, NO_ID);
        setVisibility(GONE);
        setWillNotDraw(true);
    }

 	//省略不必要代码

    public View launch() {
        if (mLayoutResource == 0) {
            L.e(TAG, "launch: ");
            return null;
        }
        return launch(mLayoutResource);
    }

    public View launch(@LayoutRes int layoutRes) {
        final View view = inflateViewNoAdd((ViewGroup) getParent(), layoutRes);
        launch(view);
        return view;
    }


    public void launch(View view) {
        if (mIsLaunched) {
            L.e(TAG, "sendView: The rocket is disposable and cannot be fired repeatedly");
            return;
        }

        final ViewGroup parent = (ViewGroup) getParent();
        final int index = parent.indexOfChild(this);
        parent.removeViewInLayout(this);
        //如果View没有id,就给view生成一个id
        if (view.getId() == View.NO_ID) {
            mInflatedId = View.generateViewId();
            view.setId(mInflatedId);
        }

        final ViewGroup.LayoutParams layoutParams = getLayoutParams();
        if (layoutParams != null) {
            parent.addView(view, index, layoutParams);
        } else {
            parent.addView(view, index);
        }

        mIsLaunched = true;
        mInflatedViewRef = new WeakReference<>(view);
    }

    private View inflateViewNoAdd(ViewGroup parent, int layoutRes) {
        final LayoutInflater factory = LayoutInflater.from(mContext);
        final View view = factory.inflate(layoutRes, parent, false);

        if (layoutRes != NO_ID) {
            view.setId(layoutRes);
        }
        return view;
    }

    public void setVisibility(int visibility) {
        if (mInflatedViewRef != null) {
            View view = mInflatedViewRef.get();
            if (view != null) {
                view.setVisibility(visibility);
            } else {
                throw new IllegalStateException("setVisibility called on un-referenced view");
            }
        } else {
            super.setVisibility(visibility);
            if (visibility == VISIBLE || visibility == INVISIBLE) {
                launch();
            }
        }
    }

    public boolean isLaunched() {
        return mIsLaunched;
    }
}

attr属性,兼容ViewStub本身的属性:

	<declare-styleable name="ViewRocket">
        <attr name="inflatedId" format="reference" />
        <attr name="layout" format="reference" />
    </declare-styleable>

方法几乎和ViewStub一模一样,但是我扩展了几个方法:


/**
* 在布局中设置了layout,或通过调用setLayoutResource后调用。
*/
	public View launch() {
        if (mLayoutResource == 0) {
            L.e(TAG, "launch: ");
            return null;
        }
        return launch(mLayoutResource);
    }
	
	/**
	* 直接引入布局,不需要在xml或调用setLayoutResource设置layoutId
	*/
    public View launch(@LayoutRes int layoutRes) {
        final View view = inflateViewNoAdd((ViewGroup) getParent(), layoutRes);
        launch(view);
        return view;
    }

	/**
	* 核心方法,可以在内部或外部,直接传入目标View
	*/
   public void launch(View view) {
        if (mIsLaunched) {
            L.e(TAG, "sendView: The rocket is disposable and cannot be fired repeatedly");
            return;
        }

        final ViewGroup parent = (ViewGroup) getParent();
        final int index = parent.indexOfChild(this);
        parent.removeViewInLayout(this);
        final ViewGroup.LayoutParams layoutParams = getLayoutParams();
        if (layoutParams != null) {
            parent.addView(view, index, layoutParams);
        } else {
            parent.addView(view, index);
        }

        mIsLaunched = true;
        mInflatedViewRef = new WeakReference<>(view);
    }

可以看到,关键方法我加了参数,这样不管在内部或外部都可以直接调用传入目标View,这样我们在实际使用就变成这样:

  • 1.在布局中使用ViewRocket和ViewStub同样的方式占位;
  • 2.在代码中:
View view = new CustomComplicateView(context);
viewRocket.launch(view);

怎么样,是不是如此的丝滑柔顺!这样就解决了我们的第一个痛点,动态添加构造出来的View,不需要额外添加一个xml!

如此解决了这个问题,我开心的大喊,我真是个小聪明!

5. 随之而来的问题!

当我开开心心的在项目中使用时候,突然发现出问题了。

我的场景是,约束布局中有两个自定义View A,B使用ViewRocket引入,A、B有依赖关系。正常操作就是B的ViewRocket依赖A的ViewRocket,但是我这么用的时候,launch之后发现出问题了。两个View位置根本不对!
吓我一跳,有问题?我赶紧替换了ViewStub重试,发现ViewStub和我一样的问题。

什么原理呢?

正好我前两天研究过约束布局,知道动态建立约束需要connect目标View的Id;另外我们在launch时候,如果不主动设置inflateId或之前给目标View设置过id,目标View是没有id的!

也就是说,我的ViewRocket在引入目标view之后,和ViewRocket的约束关系,没有转移到新添加的VIew上!所以导致布局出现错乱。

那么,我是不是可以把ViewRocket的id设置给新的View呢?

6.解决约束布局约束失效

 public void launch(View view) {
        if (mIsLaunched) {
            L.e(TAG, "sendView: The rocket is disposable and cannot be fired repeatedly");
            return;
        }

        final ViewGroup parent = (ViewGroup) getParent();
        final int index = parent.indexOfChild(this);
        parent.removeViewInLayout(this);
        //如果parentView是约束布局,把自身id设置给运送的View
        if (parent instanceof ConstraintLayout) {
            mInflatedId = this.getId();
            view.setId(mInflatedId);
        }
        //如果View没有id,就给view生成一个id
        if (view.getId() == View.NO_ID) {
            mInflatedId = View.generateViewId();
            view.setId(mInflatedId);
        }

        final ViewGroup.LayoutParams layoutParams = getLayoutParams();
        if (layoutParams != null) {
            parent.addView(view, index, layoutParams);
        } else {
            parent.addView(view, index);
        }

        mIsLaunched = true;
        mInflatedViewRef = new WeakReference<>(view);
    }

在项目里重新尝试,可以了!!!完美,哈哈哈哈

7.总结

一顿操作,就解决了ViewStub的两个问题:

  • 只能引入layout而不能动态添加new出来的View的问题;
  • 解决约束布局约束失效的问题。

其实这个这个问题比较简单,相信各位大佬轻松可以想到解决方案。不过不管问题是复杂还是简单,作为一个小趴菜,最重要的还是要有发现问题是,自己去努力探索去尝试的态度。每天都去靠自己的努力和思考解决问题,这样就能不断进步,不断提高,直到有一天能和各位大佬一样,嘿嘿嘿😏😏😏

完整代码:


import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.IdRes;
import androidx.annotation.LayoutRes;
import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;

import com.avatr.ivi.enoch.R;
import com.avatr.ivi.enochsdk.base.tools.L;

import java.lang.ref.WeakReference;

/**
 * Created By : Wilson.lu
 * Created At : 2022/12/2 at 22:16
 * Name       : View火箭.因为像一次性火箭把人送到空间站,所以命名
 * Abilities  : ViewStub的Pro版本;
 * <p>
 * 支持ViewStub本身的功能;
 * 可以动态添加单个View,不用再为单个View写一个xml布局
 * 修复约束布局使用ViewStub 约束失效的问题;
 * <p>
 * <p>
 * 用法eg:
 *
 * <p>
 * viewRocket.setLayoutResource(R.layout.layout_any_layout);
 * viewRocket.launch();
 * <p>
 * View customView = new CustomView(context);
 * viewRocket.launch(customView)
 * <p>
 * viewRocket.launch(R.layout.layout_any_layout);
 * <p>
 * 注意:暂时不支持<merge>标签
 */
public class ViewRocket extends View {
    private static final L.Tag TAG = new L.Tag("ViewRocket");
    private Context mContext;
    private WeakReference<View> mInflatedViewRef;
    @LayoutRes
    private int mLayoutResource;
    @IdRes
    private int mInflatedId = NO_ID;


    public ViewRocket(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
        mContext = context;
    }

    public ViewRocket(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewRocket);
        mLayoutResource = a.getResourceId(R.styleable.ViewRocket_layout, 0);
        mInflatedId = a.getResourceId(R.styleable.ViewRocket_inflatedId, NO_ID);
        setVisibility(GONE);
        setWillNotDraw(true);
    }

    public void setInflatedId(@IdRes int inflatedId) {
        mInflatedId = inflatedId;
    }

    /**
     * 获取要运送的View的id,没有launch之前获取不到真正的值
     *
     * @return
     */
    public int getInflatedId() {
        return mInflatedId;
    }

    public void setLayoutResource(@LayoutRes int layoutResource) {
        mLayoutResource = layoutResource;
    }

    public View launch() {
        if (mLayoutResource == 0) {
            L.e(TAG, "launch: ");
            return null;
        }
        return launch(mLayoutResource);
    }

    public View launch(@LayoutRes int layoutRes) {
        final View view = inflateViewNoAdd((ViewGroup) getParent(), layoutRes);
        launch(view);
        return view;
    }


    public void launch(View view) {
        if (mIsLaunched) {
            L.e(TAG, "sendView: The rocket is disposable and cannot be fired repeatedly");
            return;
        }

        final ViewGroup parent = (ViewGroup) getParent();
        final int index = parent.indexOfChild(this);
        parent.removeViewInLayout(this);


        if (parent instanceof ConstraintLayout) {
            //如果parentView是约束布局,把自身id设置给运送的View
            mInflatedId = this.getId();
            view.setId(mInflatedId);
        }
        //如果View没有id,就给view生成一个id
        if (view.getId() == View.NO_ID) {
            mInflatedId = View.generateViewId();
            view.setId(mInflatedId);
        }

        final ViewGroup.LayoutParams layoutParams = getLayoutParams();
        if (layoutParams != null) {
            parent.addView(view, index, layoutParams);
        } else {
            parent.addView(view, index);
        }

        mIsLaunched = true;
        mInflatedViewRef = new WeakReference<>(view);
    }

    private View inflateViewNoAdd(ViewGroup parent, int layoutRes) {
        final LayoutInflater factory = LayoutInflater.from(mContext);
        final View view = factory.inflate(layoutRes, parent, false);

        if (layoutRes != NO_ID) {
            view.setId(layoutRes);
        }
        return view;
    }

    public void setVisibility(int visibility) {
        // 当真正的布局文件被加载之后
        if (mInflatedViewRef != null) {
            // 获取到当前的View
            View view = mInflatedViewRef.get();
            if (view != null) {
                //操纵当前View的可见行
                view.setVisibility(visibility);
            } else {
                throw new IllegalStateException("setVisibility called on un-referenced view");
            }
        } else {
            //没有调用inflate的话,会设置可见性
            super.setVisibility(visibility);
            //当 当前设置可见性为 VISIBLE或者INVISIBLE的时候,会调用inflate方法。
            if (visibility == VISIBLE || visibility == INVISIBLE) {
                launch();
            }
        }
    }

    @Override
    public void draw(Canvas canvas) {
        super.draw(null);
    }

    private boolean mIsLaunched = false;

    public boolean isLaunched() {
        return mIsLaunched;
    }
}


安卓布局本质:从XML到GPU渲染的性能底层逻辑 安卓布局不是简单的UI排版,而是运行时渲染管线的声明式指令集,直接影响measure/layout/draw三阶段性能开销。其核心原理在于View树构建、约束求解与生命周期协同——`RelativeLayout`依赖相对定位模型适配TV焦点导航,`ConstraintLayout`通过显式约束网络提升动画与动态页面可控性。技术价值体现在首屏加载优化(如`ViewStub`节省300ms)、滑动帧率提升(View树深度每减1层,measure耗时降约8%)及跨端一致性保障(折叠屏/RTL/厂商ROM兼容)。典 阅读详情

相关推荐

安卓布局本质:ViewGroup约束系统与页面生命周期耦合

安卓布局不是静态UI描述,而是由ViewGroup驱动的动态约束系统,其核心在于measure-layout-draw三阶段与Activity/Fragment生命周期深度耦合。理解ConstraintLayout的约束求解原理、LinearLayout的weight分配机制及RelativeLayout的依赖图局限,是应对设备碎片化、折叠屏适配、TV焦点导航等工程挑战的基础。布局性能瓶颈(如嵌套过深、onMeasure耗时超标)往往源于对ViewGroup测量逻辑和MeasureSpec传递规则的误读。掌

weixin_34348805的博客 420

Android UI性能优化——ViewStub和Merge的使用

ViewStub虽然实现简单,但是源码设计巧妙。对于页面中的不常用布局使用ViewSutb懒加载有一定的优化效果。正确的使用merge可以有效的减少布局层级,提高页面渲染速度。但是merge使用限制比较多,应用场景比较少。

Hunter2916的博客 2020

第 18 章 C++ 关键特性速览

本章作为C++全卷收尾和后续索引,系统梳理了C++11/14/17/20/23各版本的重要特性。通过"使用频度×重要性"双维度评分体系(⭐⭐⭐⭐⭐至⭐),配合难度标识(🟢🟡🔴),帮助开发者快速掌握83项核心特性。重点包括C++11的智能指针、移动语义,C++17的结构化绑定、if constexpr,以及C++20的协程等。每项特性提供最小代码示例和详细学习指引(卷×章),并附特性依赖图和使用场景索引,为现代C++开发提供全景式学习路线图。

杨充 1314

安卓性能优化之界面UI优化——ViewStub

id属性是viewstub的id,可以通过findViewById找到该资源控件,inflatedId是root view id,即viewstub调用inflate之后,不能再次调用,会报错,因为viewstub已经从视图层级中被移除,但是可以通过inflatedId取得viewstub的布局,继续来控制隐藏viewstub,也就是inflatedId的布局和viewstub的布局保持一致。view_stub.xml如下。3、ViewStub针对的事layout布局,不是某一个具体的view;

m0_62089210的博客 1833

ConstraintLayout子View设置match_parent后约束失效

ConstraintLayout布局中设置了match_parent就会铺满整个屏幕,约束属性也会失效,是因为约束布局不支持match_parent属性,所以想要实现match_parent的效果,先把大小设置位0dp,然后添加约束条件。如下: <View android:id="@+id/devide_line" android:layout_width="match_parent" android:layout_height="@dimen/dp_1

海阔天空 2794

拼多多Android面试题及参考答案

Android 系统采用分层架构设计,从底层到上层依次为Linux 内核层硬件抽象层(HAL)系统运行时库层应用框架层应用层,共五层。位置:Linux 内核层与系统运行时库层之间,是“硬件与系统的隔离层”。核心作用:屏蔽硬件差异,为上层提供统一的硬件访问接口,解决 Android 开源与硬件厂商闭源的矛盾,具体功能如下:硬件能力抽象:将硬件的具体实现(如不同厂商的摄像头芯片、音频芯片)封装为标准化接口(如Camera HALAudio HAL。

大模型大数据攻城狮的专栏 1279

ConstraintLayout使用总结

ConstraintLayout使用过程中遇到问题记录 android:layout_width="0dp" 效果是一样的,可以自动充满剩余空间 约束布局ConstraintLayout看这一篇就够了 哲♂学三幻神带你学习ConstraintLayout(约束布局) ......

“点点”专栏 4998

【Unity Animation 2D】从零到一:骨骼绑定与IK动画实战指南

本文详细介绍了Unity中2D骨骼绑定与IK动画的实战流程,从角色素材准备、骨骼系统创建到权重绘制和IK设置,逐步指导开发者掌握2D动画制作技巧。通过反向动力学(IK)技术,实现更自然的角色动画效果,适用于游戏开发和动画制作。

weixin_30430677的博客 369

Android屏幕适配实战:ConstraintLayout解决多控件被挤出屏幕问题

在Android应用开发中,屏幕适配是构建高质量用户界面的核心挑战之一。传统布局方式如LinearLayout和RelativeLayout采用静态思维,依赖固定尺寸和复杂嵌套,难以应对不同屏幕尺寸和动态内容变化,常导致控件重叠或被挤出屏幕。ConstraintLayout(约束布局)通过声明式的关系约束机制,从根本上改变了这一局面。其核心原理是让控件通过建立与父容器、兄弟控件之间的约束关系来动态定位,结合偏斜(Bias)和链条(Chains)等特性,能够实现灵活的空间分配与自适应排列。这种技术方案显著提升

weixin_33816946的博客 383

Unity3D 2022版如何一键安装Newtonsoft.Json?最新Package Manager教程

本文详细介绍了在Unity3D 2022及以上版本中,如何通过Package Manager一键安装和配置Newtonsoft.Json(Json.NET)。教程涵盖了从打开Package Manager、搜索并安装正确包源,到验证安装和基础使用的完整流程,旨在帮助开发者以现代化、可靠的方式集成这一强大的JSON处理框架,提升开发效率并规避手动导入的常见问题。

lg888的博客 920

FPGA+滨淞CCD实战:手把手教你搭建S7031/S10142成像电路(附避坑指南)

本文详细介绍了使用FPGA驱动滨淞CCD传感器(S7031/S10142)构建高精度成像系统的完整实战流程。内容涵盖核心器件选型、系统架构规划、电路板(焦面板与接口板)设计要点、FPGA Verilog逻辑实现,并提供了从电源管理、信号调理到数据采集的详细避坑指南,帮助读者从零搭建稳定可靠的成像电路。

weixin_29271263的博客 227

Android Studio 布局实战:六大布局详解与场景应用

本文详细解析Android Studio中六大核心布局(线性、相对、约束、帧、表格、网格)的特性与实战应用技巧。通过具体代码示例和场景分析,帮助开发者掌握如何选择合适的布局方案,优化界面性能并提升开发效率,特别适合Android界面开发初学者和进阶者参考。

m2n3o4p5的博客 213

SPSS实战:5步搞定多指标联合诊断的ROC曲线分析(附Logistic回归教程)

本文详细介绍了如何使用SPSS软件进行多指标联合诊断的ROC曲线分析。通过5个实战步骤,从数据准备、二元Logistic回归建模生成综合预测概率,到绘制并比较单指标与联合模型的ROC曲线,帮助临床研究者有效整合多个诊断指标,提升模型判别能力(AUC),实现诊断效能的优化。

mqtt6iot的博客 1422

姿势摆好,一招学会android的布局优化!

作为android应用来讲,无论应用本身多么美观,功能多么强大,内容多么丰富。但如果App本身打开界面缓慢超过手机16ms刷新一次页面的时间,就会产生卡顿。用户体验都会变得极差,导致用户量减少。所以我们在开发过程中同样要注重布局优化。 标签 在Layout布局中如果有你想要引用的布局时,若该布局在不同的布局是公共布局,我们会多次使用到。这时可以使用标签。并且便于统一的修改与查看。 &lt;...

weixin_33985507的博客 111

少儿学编程系列--使用递归画出龙形曲线DragonCurve

龙形曲线(Dragon Curve) 龙形曲线(Dragon Curve)又叫分形龙,是一种自相似碎形曲线的统称,因形似龙的蜿蜒盘曲而得名 使用黄金比例画龙形曲线 根据黄金比例画一条龙曲线。每次递归,第一步向左转32.89度,移动0.74倍原距离;第二个递归步骤向右转46.99度,移动原来距离的0.55倍。请查看这个网页(https://larryriddle.agnesscott.org/ifs/heighway/goldenDragon.htm)了解关于龙形黄线数学的细节 龙形曲线效果 .

penriver的博客 2296

Python爬虫实战:逆向解析腾讯视频API,高效获取结构化视频信息

首先,我们定义期望获取的数据结构,这有助于数据验证和代码清晰度。python"""腾讯视频信息数据模型"""title: str = Field(..., description="视频标题")vid: str = Field(..., description="视频唯一ID")play_count: Optional[int] = Field(None, description="播放量")

2201_76125261的博客 2378

学习安卓-第二周-代码优化

1. 代码优化 (1)定义变量要时刻注意作用域。如果一个变量只在一个方法中使用,那就不需要设为全局变量,设为方法内的局部变量即可。 (2)代码简洁。 1,首先就是不创建无用的变量,例如View view=binding.getroot();return view; 如果这里view使用很少,甚至只有一次,那就没有创建的意义,浪费资源。直接return binding.getroot();。 2,删除废代码(注释了的代码,多余的注释),使得代码美观 3,变量命名规范,驼峰命名法,且名称不宜过长 4,!!!代码

qq_46577594的博客 220

企业级OpenClaw部署实战:10个关键配置让你从“养虾“到“精通“

OpenClaw(社区昵称"小龙虾")作为2026年开源社区最活跃的AI Agent项目,凭借其多Agent协同架构和企业级安全隔离能力,已成为中大型企业的首选AI基础设施。本文基于真实企业部署经验,系统梳理了企业级OpenClaw部署的10个关键配置:用户ID消息隔离、群组ID消息隔离、多机器人配置、无限量Agent创建、机器人与Agent关联、Agent间调度、权限配置、Hooks任务、多模型切换及一键备份迁移。

program哲学 929

安卓RelativeLayout实战:页面意图与布局关系的深度解析

Android布局系统本质是将页面交互意图映射为UI空间关系的技术体系。从FrameLayout的坐标原点、LinearLayout的线性流式、RelativeLayout的关系驱动,到ConstraintLayout的约束网络,不同布局引擎对应不同的测量复杂度与语义表达能力。RelativeLayout凭借`layout_above`、`layout_below`等直观属性,在TV端焦点导航、横竖屏适配、动态关系调整等场景中展现出不可替代的语义清晰性与低学习成本。其与Activity/Fragment的生

weixin_33946605的博客 437

Python内置函数zip()——《Python内置函数手册》Python老吕编著

zip()函数是 Python 中的一个内置函数,用于将多个可迭代对象(如列表、元组、字符串等)中对应的元素打包成一个个元组,然后返回由这些元组组成的迭代器。zip()函数常用于将多个序列并行迭代,或者用于数据对齐。zip()它返回一个迭代器,这意味着你可以逐个获取打包后的元素,而不是一次性将所有元素加载到内存中。如果输入的可迭代对象长度不一致,zip()会以最短的可迭代对象为准,只打包到最短对象的末尾。它可以用来并行迭代多个序列,使得代码更加简洁和高效。在接下来的章节中,我们将详细介绍。

Python老吕的博客 680

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

2018春招360 Android笔试客观题复盘:考点、原理与面试价值

在Android开发者的技术成长路径中,系统理解四大组件、Handler消息机制、性能优化与底层Binder通信,是构建扎实工程能力的关键。Activity启动模式决定了任务栈的复用行为,Handler的Looper调度则直接影响异步任务与主线程协作效率;而内存泄漏、ANR与布局渲染问题,更是线上稳定性的核心指标。这些概念看似独立,实则共同构成Android运行时体系的基础逻辑。从经典笔试题目出发,结合真实开发场景,可以更清晰地理解系统源码的运作方式,并把握版本演进对技术选型的影响。本文以360春招Andr

cmff98425的博客 385

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

Android 技术文章合集 | Jetpack、Kotlin、Window & View、事件机制、多线程、存储等

代码说」:一个专注于 Android 技术细节拆解的分享号,欢迎关注!

小马快跑的博客 931

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

android 面试题

ContextImpl.startActivity → Instrumentation.execStartActivity → AMS.startActivity → 校验权限、创建 ActivityRecord、Pause 当前 Activity → ApplicationThread.scheduleTransaction → ActivityThread.H → LaunchActivityItem → performLaunchActivity(创建 Activity、调用 onCreate)。

qq_25657125的博客 389

京东Android校招笔试复盘:核心考点与备考指南

在Android开发岗位的校招中,笔试环节往往考察的是开发者对底层原理与基础知识的掌握程度。从Java集合框架到并发编程,从Handler消息机制到Activity启动模式,这些核心技术点贯穿于日常开发与面试考核之中。理解HashMap的底层实现、线程池的运行流程、Binder的通信原理,不仅能应对大厂笔试,更能提升实际工程中的问题排查能力。本文基于京东2019校招Android笔试题,系统梳理了Java基础、Android核心机制、数据结构与算法、网络协议等高频考点,并针对备考优先级、常见陷阱和技术面追问

weixin_30509393的博客 397

2024秋招移动端笔试复盘:性能优化与厂商生态考点全拆解

移动端开发不仅关乎功能实现,更涉及性能、系统与生态的复杂博弈。从应用冷启动、列表滑动流畅度,到系统定制ROM的后台策略,再到应用签名机制(如sha1与包名绑定)和安全校验,每个环节都决定App在真实设备上的体验。厂商笔试往往以这些工程问题为核心,考察开发者对Android底层原理、渲染机制、内存管理以及跨端框架的掌握程度。借助vConsole等调试工具进行现场排障,也是移动端工程师的必备技能。本文基于2024年秋招移动端笔试的复盘,梳理了从热搜词反推的考察方向、性能优化核心考点、厂商定制ROM隐藏考点、跨端

weixin_34116482的博客 238
上一篇: Android插件化原理(二)—启动插件Activity
下一篇: maven-publish 如何发布项目到maven
cry kid
博客等级 码龄10年 144粉丝 24原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值