public abstract class

ConstraintHelper

extends View

 java.lang.Object

↳View

↳androidx.constraintlayout.widget.ConstraintHelper

Subclasses:

Layer, Flow, MotionPlaceholder, Carousel, Grid, MotionEffect, CircularFlow, VirtualLayout, Barrier, Group, MotionHelper

Gradle dependencies

compile group: 'androidx.constraintlayout', name: 'constraintlayout', version: '2.2.0-beta01'

  • groupId: androidx.constraintlayout
  • artifactId: constraintlayout
  • version: 2.2.0-beta01

Artifact androidx.constraintlayout:constraintlayout:2.2.0-beta01 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.constraintlayout:constraintlayout com.android.support.constraint:constraint-layout

Androidx class mapping:

androidx.constraintlayout.widget.ConstraintHelper android.support.constraint.ConstraintHelper

Overview

Added in 1.1

This class manages a set of referenced widgets. HelperWidget objects can be created to act upon the set of referenced widgets. The difference between ConstraintHelper and ViewGroup is that multiple ConstraintHelper can reference the same widgets.

Widgets are referenced by being added to a comma separated list of ids, e.g.:

     
         
     
     

Summary

Fields
protected static final java.lang.StringCHILD_TAG

protected intmCount

protected HelpermHelperWidget

protected int[]mIds

protected java.util.HashMap<java.lang.Integer, java.lang.String>mMap

protected java.lang.StringmReferenceIds

protected java.lang.StringmReferenceTags

protected booleanmUseViewMeasure

protected ContextmyContext

Constructors
publicConstraintHelper(Context context)

publicConstraintHelper(Context context, AttributeSet attrs)

publicConstraintHelper(Context context, AttributeSet attrs, int defStyleAttr)

Methods
public voidaddView(View view)

Add a view to the helper.

public voidapplyHelperParams()

hook for helpers to apply parameters in MotionLayout

protected voidapplyLayoutFeatures()

protected voidapplyLayoutFeatures(ConstraintLayout container)

protected voidapplyLayoutFeaturesInConstraintSet(ConstraintLayout container)

public booleancontainsId(int id)

does id table contain the id

public int[]getReferencedIds()

Helpers typically reference a collection of ids

protected ViewgetViews(ConstraintLayout layout)

public intindexFromId(int id)

find the position of an id

protected voidinit(AttributeSet attrs)

public static booleanisChildOfHelper(View v)

public voidloadParameters(ConstraintSet.Constraint constraint, HelperWidget child, ConstraintLayout.LayoutParams layoutParams, <any> mapIdToWidget)

Load the parameters

protected voidonAttachedToWindow()

public voidonDraw(Canvas canvas)

protected voidonMeasure(int widthMeasureSpec, int heightMeasureSpec)

public intremoveView(View view)

Remove a given view from the helper.

public voidresolveRtl(ConstraintWidget widget, boolean isRtl)

resolve the RTL

protected voidsetIds(java.lang.String idList)

public voidsetReferencedIds(int[] ids[])

Helpers typically reference a collection of ids

protected voidsetReferenceTags(java.lang.String tagList)

public voidsetTag(int key, java.lang.Object tag)

public voidupdatePostConstraints(ConstraintLayout container)

update after constraints are resolved

public voidupdatePostLayout(ConstraintLayout container)

Allows a helper a chance to update its internal object post layout or set up connections for the pointed elements

public voidupdatePostMeasure(ConstraintLayout container)

public voidupdatePreDraw(ConstraintLayout container)

called before the draw

public voidupdatePreLayout(ConstraintLayout container)

Allows a helper a chance to update its internal object pre layout or set up connections for the pointed elements

public voidupdatePreLayout(ConstraintWidgetContainer container, Helper helper, <any> map)

called before solver resolution

public voidvalidateParams()

Allows a helper to replace the default ConstraintWidget in LayoutParams by its own subclass

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Fields

protected int[] mIds

protected int mCount

protected Context myContext

protected Helper mHelperWidget

protected boolean mUseViewMeasure

protected java.lang.String mReferenceIds

protected java.lang.String mReferenceTags

protected static final java.lang.String CHILD_TAG

protected java.util.HashMap<java.lang.Integer, java.lang.String> mMap

Constructors

public ConstraintHelper(Context context)

public ConstraintHelper(Context context, AttributeSet attrs)

public ConstraintHelper(Context context, AttributeSet attrs, int defStyleAttr)

Methods

protected void init(AttributeSet attrs)

protected void onAttachedToWindow()

public void addView(View view)

Add a view to the helper. The referenced view need to be a child of the helper's parent. The view also need to have its id set in order to be added.

Parameters:

view:

public int removeView(View view)

Remove a given view from the helper.

Parameters:

view:

Returns:

index of view removed

public int[] getReferencedIds()

Helpers typically reference a collection of ids

Returns:

ids referenced

public void setReferencedIds(int[] ids[])

Helpers typically reference a collection of ids

public void onDraw(Canvas canvas)

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)

public void validateParams()

Allows a helper to replace the default ConstraintWidget in LayoutParams by its own subclass

protected void setIds(java.lang.String idList)

protected void setReferenceTags(java.lang.String tagList)

protected void applyLayoutFeatures(ConstraintLayout container)

Parameters:

container:

protected void applyLayoutFeatures()

protected void applyLayoutFeaturesInConstraintSet(ConstraintLayout container)

public void updatePreLayout(ConstraintLayout container)

Allows a helper a chance to update its internal object pre layout or set up connections for the pointed elements

Parameters:

container:

public void updatePreLayout(ConstraintWidgetContainer container, Helper helper, <any> map)

called before solver resolution

Parameters:

container:
helper:
map:

protected View getViews(ConstraintLayout layout)

public void updatePostLayout(ConstraintLayout container)

Allows a helper a chance to update its internal object post layout or set up connections for the pointed elements

Parameters:

container:

public void updatePostMeasure(ConstraintLayout container)

Parameters:

container:

public void updatePostConstraints(ConstraintLayout container)

update after constraints are resolved

Parameters:

container:

public void updatePreDraw(ConstraintLayout container)

called before the draw

Parameters:

container:

public void loadParameters(ConstraintSet.Constraint constraint, HelperWidget child, ConstraintLayout.LayoutParams layoutParams, <any> mapIdToWidget)

Load the parameters

Parameters:

constraint:
child:
layoutParams:
mapIdToWidget:

public void resolveRtl(ConstraintWidget widget, boolean isRtl)

resolve the RTL

Parameters:

widget:
isRtl:

public void setTag(int key, java.lang.Object tag)

public boolean containsId(int id)

does id table contain the id

Parameters:

id:

Returns:

public int indexFromId(int id)

find the position of an id

Parameters:

id:

Returns:

public void applyHelperParams()

hook for helpers to apply parameters in MotionLayout

public static boolean isChildOfHelper(View v)

Source

/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.constraintlayout.widget;

import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;

import androidx.annotation.NonNull;
import androidx.constraintlayout.core.widgets.ConstraintWidget;
import androidx.constraintlayout.core.widgets.ConstraintWidgetContainer;
import androidx.constraintlayout.core.widgets.Helper;
import androidx.constraintlayout.core.widgets.HelperWidget;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashMap;

/**
 *
 * <b>Added in 1.1</b>
 * <p>
 *     This class manages a set of referenced widgets. HelperWidget objects can be
 *     created to act upon the set
 *     of referenced widgets. The difference between {@code ConstraintHelper} and
 *     {@code ViewGroup} is that
 *     multiple {@code ConstraintHelper} can reference the same widgets.
 * <p>
 *     Widgets are referenced by being added to a comma separated list of ids, e.g.:
 *     <pre>
 *     {@code
 *         <androidx.constraintlayout.widget.Barrier
 *              android:id="@+id/barrier"
 *              android:layout_width="wrap_content"
 *              android:layout_height="wrap_content"
 *              app:barrierDirection="start"
 *              app:constraint_referenced_ids="button1,button2" />
 *     }
 *     </pre>
 * </p>
 */
public abstract class ConstraintHelper extends View {

    /**
     *
     */
    protected int[] mIds = new int[32];
    /**
     *
     */
    protected int mCount;

    /**
     *
     */
    protected Context myContext;
    /**
     *
     */
    protected Helper mHelperWidget;
    /**
     *
     */
    protected boolean mUseViewMeasure = false;
    /**
     *
     */
    protected String mReferenceIds;
    /**
     *
     */
    protected String mReferenceTags;

    /**
     *
     */
    private View[] mViews = null;

    /**
     *
     */
    protected final static String CHILD_TAG = "CONSTRAINT_LAYOUT_HELPER_CHILD";

    protected HashMap<Integer, String> mMap = new HashMap<>();

    public ConstraintHelper(Context context) {
        super(context);
        myContext = context;
        init(null);
    }

    public ConstraintHelper(Context context, AttributeSet attrs) {
        super(context, attrs);
        myContext = context;
        init(attrs);
    }

    public ConstraintHelper(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        myContext = context;
        init(attrs);
    }

    /**
     *
     */
    protected void init(AttributeSet attrs) {
        if (attrs != null) {
            TypedArray a = getContext().obtainStyledAttributes(attrs,
                    R.styleable.ConstraintLayout_Layout);
            final int count = a.getIndexCount();
            for (int i = 0; i < count; i++) {
                int attr = a.getIndex(i);
                if (attr == R.styleable.ConstraintLayout_Layout_constraint_referenced_ids) {
                    mReferenceIds = a.getString(attr);
                    setIds(mReferenceIds);
                } else if (attr == R.styleable.ConstraintLayout_Layout_constraint_referenced_tags) {
                    mReferenceTags = a.getString(attr);
                    setReferenceTags(mReferenceTags);
                }
            }
            a.recycle();
        }
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        if (mReferenceIds != null) {
            setIds(mReferenceIds);
        }
        if (mReferenceTags != null) {
            setReferenceTags(mReferenceTags);
        }
    }

    /**
     * Add a view to the helper. The referenced view need to be a child of the helper's parent.
     * The view also need to have its id set in order to be added.
     *
     * @param view
     */
    public void addView(View view) {
        if (view == this) {
            return;
        }
        if (view.getId() == -1) {
            Log.e("ConstraintHelper", "Views added to a ConstraintHelper need to have an id");
            return;
        }
        if (view.getParent() == null) {
            Log.e("ConstraintHelper", "Views added to a ConstraintHelper need to have a parent");
            return;
        }
        mReferenceIds = null;
        addRscID(view.getId());
        requestLayout();
    }

    /**
     * Remove a given view from the helper.
     *
     * @param view
     * @return index of view removed
     */
    public int removeView(View view) {
        int index = -1;
        int id = view.getId();
        if (id == -1) {
            return index;
        }
        mReferenceIds = null;
        for (int i = 0; i < mCount; i++) {
            if (mIds[i] == id) {
                index = i;
                for (int j = i; j < mCount - 1; j++) {
                    mIds[j] = mIds[j + 1];
                }
                mIds[mCount - 1] = 0;
                mCount--;
                break;
            }
        }
        requestLayout();
        return index;
    }

    /**
     * Helpers typically reference a collection of ids
     * @return ids referenced
     */
    public int[] getReferencedIds() {
        return Arrays.copyOf(mIds, mCount);
    }

    /**
     * Helpers typically reference a collection of ids
     */
    public void setReferencedIds(int[] ids) {
        mReferenceIds = null;
        mCount = 0;
        for (int i = 0; i < ids.length; i++) {
            addRscID(ids[i]);
        }
    }

    /**
     *
     */
    private void addRscID(int id) {
        if (id == getId()) {
            return;
        }
        if (mCount + 1 > mIds.length) {
            mIds = Arrays.copyOf(mIds, mIds.length * 2);
        }
        mIds[mCount] = id;
        mCount++;
    }

    /**
     *
     */
    @Override
    public void onDraw(@NonNull Canvas canvas) {
        // Nothing
    }

    /**
     *
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mUseViewMeasure) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        } else {
            setMeasuredDimension(0, 0);
        }
    }

    /**
     *
     * Allows a helper to replace the default ConstraintWidget in LayoutParams by its own subclass
     */
    public void validateParams() {
        if (mHelperWidget == null) {
            return;
        }
        ViewGroup.LayoutParams params = getLayoutParams();
        if (params instanceof ConstraintLayout.LayoutParams) {
            ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) params;
            layoutParams.mWidget = (ConstraintWidget) mHelperWidget;
        }
    }

    /**
     *
     */
    private void addID(String idString) {
        if (idString == null || idString.length() == 0) {
            return;
        }
        if (myContext == null) {
            return;
        }

        idString = idString.trim();

        int rscId = findId(idString);
        if (rscId != 0) {
            mMap.put(rscId, idString); // let's remember the idString used,
            // as we may need it for dynamic modules
            addRscID(rscId);
        } else {
            Log.w("ConstraintHelper", "Could not find id of \"" + idString + "\"");
        }
    }

    /**
     *
     */
    private void addTag(String tagString) {
        if (tagString == null || tagString.length() == 0) {
            return;
        }
        if (myContext == null) {
            return;
        }

        tagString = tagString.trim();

        ConstraintLayout parent = null;
        if (getParent() instanceof ConstraintLayout) {
            parent = (ConstraintLayout) getParent();
        }
        if (parent == null) {
            Log.w("ConstraintHelper", "Parent not a ConstraintLayout");
            return;
        }
        int count = parent.getChildCount();
        for (int i = 0; i < count; i++) {
            View v = parent.getChildAt(i);
            ViewGroup.LayoutParams params = v.getLayoutParams();
            if (params instanceof ConstraintLayout.LayoutParams) {
                ConstraintLayout.LayoutParams lp = (ConstraintLayout.LayoutParams) params;
                if (tagString.equals(lp.constraintTag)) {
                    if (v.getId() == View.NO_ID) {
                        Log.w("ConstraintHelper", "to use ConstraintTag view "
                                + v.getClass().getSimpleName() + " must have an ID");
                    } else {
                        addRscID(v.getId());
                    }
                }
            }

        }
    }

    /**
     * Attempt to find the id given a reference string
     * @param referenceId
     * @return
     */
    private int findId(String referenceId) {
        ConstraintLayout parent = null;
        if (getParent() instanceof ConstraintLayout) {
            parent = (ConstraintLayout) getParent();
        }
        int rscId = 0;

        // First, if we are in design mode let's get the cached information
        if (isInEditMode() && parent != null) {
            Object value = parent.getDesignInformation(0, referenceId);
            if (value instanceof Integer) {
                rscId = (Integer) value;
            }
        }

        // ... if not, let's check our siblings
        if (rscId == 0 && parent != null) {
            // TODO: cache this in ConstraintLayout
            rscId = findId(parent, referenceId);
        }

        if (rscId == 0) {
            try {
                Class res = R.id.class;
                Field field = res.getField(referenceId);
                rscId = field.getInt(null);
            } catch (Exception e) {
                // Do nothing
            }
        }

        if (rscId == 0) {
            // this will first try to parse the string id as a number (!) in ResourcesImpl, so
            // let's try that last...
            rscId = myContext.getResources().getIdentifier(referenceId, "id",
                    myContext.getPackageName());
        }

        return rscId;
    }

    /**
     * Iterate through the container's children to find a matching id.
     * Slow path, seems necessary to handle dynamic modules resolution...
     *
     * @param container
     * @param idString
     * @return
     */
    private int findId(ConstraintLayout container, String idString) {
        if (idString == null || container == null) {
            return 0;
        }
        Resources resources = myContext.getResources();
        if (resources == null) {
            return 0;
        }
        final int count = container.getChildCount();
        for (int j = 0; j < count; j++) {
            View child = container.getChildAt(j);
            if (child.getId() != -1) {
                String res = null;
                try {
                    res = resources.getResourceEntryName(child.getId());
                } catch (android.content.res.Resources.NotFoundException e) {
                    // nothing
                }
                if (idString.equals(res)) {
                    return child.getId();
                }
            }
        }
        return 0;
    }

    /**
     *
     */
    protected void setIds(String idList) {
        mReferenceIds = idList;
        if (idList == null) {
            return;
        }
        int begin = 0;
        mCount = 0;
        while (true) {
            int end = idList.indexOf(',', begin);
            if (end == -1) {
                addID(idList.substring(begin));
                break;
            }
            addID(idList.substring(begin, end));
            begin = end + 1;
        }
    }

    /**
     *
     */
    protected void setReferenceTags(String tagList) {
        mReferenceTags = tagList;
        if (tagList == null) {
            return;
        }
        int begin = 0;
        mCount = 0;
        while (true) {
            int end = tagList.indexOf(',', begin);
            if (end == -1) {
                addTag(tagList.substring(begin));
                break;
            }
            addTag(tagList.substring(begin, end));
            begin = end + 1;
        }
    }

    /**
     *
     * @param container
     */
    protected void applyLayoutFeatures(ConstraintLayout container) {
        int visibility = getVisibility();
        float elevation = 0;
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
            elevation = getElevation();
        }
        for (int i = 0; i < mCount; i++) {
            int id = mIds[i];
            View view = container.getViewById(id);
            if (view != null) {
                view.setVisibility(visibility);
                if (elevation > 0
                        && android.os.Build.VERSION.SDK_INT
                        >= android.os.Build.VERSION_CODES.LOLLIPOP) {
                    view.setTranslationZ(view.getTranslationZ() + elevation);
                }
            }
        }
    }

    /**
     *
     */
    protected void applyLayoutFeatures() {
        ViewParent parent = getParent();
        if (parent != null && parent instanceof ConstraintLayout) {
            applyLayoutFeatures((ConstraintLayout) parent);
        }
    }

    /**
     *
     */
    protected void applyLayoutFeaturesInConstraintSet(ConstraintLayout container) {}

    /**
     *
     * Allows a helper a chance to update its internal object pre layout or
     * set up connections for the pointed elements
     *
     * @param container
     */
    public void updatePreLayout(ConstraintLayout container) {
        if (isInEditMode()) {
            setIds(mReferenceIds);
        }
        if (mHelperWidget == null) {
            return;
        }
        mHelperWidget.removeAllIds();
        for (int i = 0; i < mCount; i++) {
            int id = mIds[i];
            View view = container.getViewById(id);
            if (view == null) {
                // hm -- we couldn't find the view.
                // It might still be there though, but with the wrong id (with dynamic modules)
                String candidate = mMap.get(id);
                int foundId = findId(container, candidate);
                if (foundId != 0) {
                    mIds[i] = foundId;
                    mMap.put(foundId, candidate);
                    view = container.getViewById(foundId);
                }
            }
            if (view != null) {
                mHelperWidget.add(container.getViewWidget(view));
            }
        }
        mHelperWidget.updateConstraints(container.mLayoutWidget);
    }

    /**
     * called before solver resolution
     * @param container
     * @param helper
     * @param map
     */
    public void updatePreLayout(ConstraintWidgetContainer container,
                                Helper helper,
                                SparseArray<ConstraintWidget> map) {
        helper.removeAllIds();
        for (int i = 0; i < mCount; i++) {
            int id = mIds[i];
            helper.add(map.get(id));
        }
    }

    protected View [] getViews(ConstraintLayout layout) {

        if (mViews == null || mViews.length != mCount) {
            mViews = new View[mCount];
        }

        for (int i = 0; i < mCount; i++) {
            int id = mIds[i];
            mViews[i] = layout.getViewById(id);
        }
        return mViews;
    }

    /**
     *
     * Allows a helper a chance to update its internal object post layout or
     * set up connections for the pointed elements
     *
     * @param container
     */
    public void updatePostLayout(ConstraintLayout container) {
        // Do nothing
    }

    /**
     *
     * @param container
     */
    public void updatePostMeasure(ConstraintLayout container) {
        // Do nothing
    }

    /**
     * update after constraints are resolved
     * @param container
     */
    public void updatePostConstraints(ConstraintLayout container) {
        // Do nothing
    }

    /**
     * called before the draw
     * @param container
     */
    public void updatePreDraw(ConstraintLayout container) {
        // Do nothing
    }

    /**
     * Load the parameters
     * @param constraint
     * @param child
     * @param layoutParams
     * @param mapIdToWidget
     */
    public void loadParameters(ConstraintSet.Constraint constraint,
                               HelperWidget child,
                               ConstraintLayout.LayoutParams layoutParams,
                               SparseArray<ConstraintWidget> mapIdToWidget) {
        // TODO: rethink this. The list of views shouldn't be resolved at updatePreLayout stage,
        // as this makes changing referenced views tricky at runtime
        if (constraint.layout.mReferenceIds != null) {
            setReferencedIds(constraint.layout.mReferenceIds);
        } else if (constraint.layout.mReferenceIdString != null) {
            if (constraint.layout.mReferenceIdString.length() > 0) {
                constraint.layout.mReferenceIds = convertReferenceString(
                        constraint.layout.mReferenceIdString);
            } else {
                constraint.layout.mReferenceIds = null;
            }
        }
        if (child != null) {
            child.removeAllIds();
            if (constraint.layout.mReferenceIds != null) {
                for (int i = 0; i < constraint.layout.mReferenceIds.length; i++) {
                    int id = constraint.layout.mReferenceIds[i];
                    ConstraintWidget widget = mapIdToWidget.get(id);
                    if (widget != null) {
                        child.add(widget);
                    }
                }
            }
        }
    }

    private int[] convertReferenceString(String referenceIdString) {
        String[] split = referenceIdString.split(",");
        int[] rscIds = new int[split.length];
        int count = 0;
        for (int i = 0; i < split.length; i++) {
            String idString = split[i];
            idString = idString.trim();
            int id = findId(idString);
            if (id != 0) {
                rscIds[count++] = id;
            }
        }
        if (count != split.length) {
            rscIds = Arrays.copyOf(rscIds, count);
        }
        return rscIds;
    }

    /**
     * resolve the RTL
     * @param widget
     * @param isRtl
     */
    public void resolveRtl(ConstraintWidget widget, boolean isRtl) {
        // nothing here
    }

    @Override
    public void setTag(int key, Object tag) {
        super.setTag(key, tag);
        if (tag == null && mReferenceIds == null) {
            addRscID(key);
        }
    }

    /**
     * does id table contain the id
     *
     * @param id
     * @return
     */
    public boolean containsId(final int id) {
        boolean result = false;
        for (int i : mIds) {
            if (i == id) {
                result = true;
                break;
            }
        }
        return result;
    }

    /**
     * find the position of an id
     *
     * @param id
     * @return
     */
    public int indexFromId(final int id) {
        int index = -1;
        for (int i : mIds) {
            index++;
            if (i == id) {
                return index;
            }
        }
        return index;
    }

    /**
     * hook for helpers to apply parameters in MotionLayout
     */
    public void applyHelperParams() {

    }

    public static boolean isChildOfHelper(View v) {
       return CHILD_TAG == v.getTag();
    }
}