๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

์นดํ…Œ๊ณ ๋ฆฌ ์—†์Œ

[Android] ViewGroup ๋ฌธ์„œ ์ฝ๊ธฐ

https://developer.android.com/reference/android/view/ViewGroup

 

ViewGroup  |  Android Developers

android.net.wifi.hotspot2.omadm

developer.android.com

 

ViewGroup์€ (์ž๋…€๋ผ๊ณ  ๋ถ€๋ฅด๋Š”) ๋‹ค๋ฅธ ๋ทฐ๋“ค์„ ํฌํ•จํ•  ์ˆ˜ ์žˆ๋Š” ํŠน๋ณ„ํ•œ ๋ทฐ์ž…๋‹ˆ๋‹ค. ์ด ๋ทฐ ๊ทธ๋ฃน์€ ๋ ˆ์ด์•„์›ƒ๊ณผ ๋ทฐ ์ปจํ…Œ์ด๋„ˆ๋“ค์˜ ๊ธฐ๋ณธ ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค. ์ด ํด๋ž˜์Šค๋Š” ๋˜ํ•œ ๋ ˆ์ด์•„์›ƒ ๋งค๊ฐœ๋ณ€์ˆ˜๋“ค์„ ์œ„ํ•œ ๊ธฐ๋ณธ ํด๋ž˜์Šค์ธ ViewGroup.LayoutParams ํด๋ž˜์Šค๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. 

๋ ˆ์ด์•„์›ƒ ์†์„ฑ๊ณผ ๊ด€๋ จํ•ด LayoutParams๋„ ํ™•์ธํ•˜์„ธ์š”.

 

์—ฌ๊ธฐ ์™ผ์ชฝ๊ณผ ์˜ค๋ฅธ์ชฝ ์—ฌ๋ฐฑ ์•ˆ์— ์ž๋…€๋“ค์„ ์Œ“์„(stack) ์ˆ˜ ์žˆ๋Š” ๊ฐ„๋‹จํ•œ FrameLayout์„ ๊ตฌํ˜„ํ•œ ์ปค์Šคํ…€ ViewGroup์˜ ์™„์„ฑ๋œ ๊ตฌํ˜„์ด ์žˆ์Šต๋‹ˆ๋‹ค. 

 

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RemoteViews;

/**
 * ์ปค์Šคํ…€ ๋ ˆ์ด์•„์›ƒ ๋ฉ”๋‹ˆ์ €๋ฅผ ์ž‘์„ฑํ•œ ์˜ˆ์ž…๋‹ˆ๋‹ค. ๊ฝค ์™„์ „ํ•œ ๊ธฐ๋Šฅ์˜ ๋ ˆ์ด์•„์›ƒ ๋งค๋‹ˆ์ €๋กœ
 * ๋น„๊ต์  ์ข…ํ•ฉ์ ์ด๊ณ , ๋‹ค๋ฅธ ๋ชจ๋“  ๋ ˆ์ด์•„์›ƒ๋“ค์˜ ๊ฒฝ์šฐ๋„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
 * ๋” ํŠน์ •ํ•œ ๊ฒฝ์šฐ์— ๋‹จ์ˆœํ™”์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
 */
 @RemoteViews.RemoteView
 public class CustomLayout extends ViewGroup {
     /** ์™ผ์ชฝ ์—ฌ๋ฐฑ์— ์ž๋…€๋“ค์ด ์‚ฌ์šฉํ•  ๊ณต๊ฐ„์˜ ์ˆ˜์น˜
     private int mLeftWidth;
     
     /** ์˜ค๋ฅธ์ชฝ ์—ฌ๋ฐฑ์— ์ž๋…€๋“ค์ด ์‚ฌ์šฉํ•  ๊ณต๊ฐ„์˜ ์ˆ˜์น˜
     private int mRightWidth;
     
     /** ์ž๋…€๋“ค์˜ gravity์— ๋”ฐ๋ผ ์ž๋…€ ํ”„๋ ˆ์ž„์„ ๊ณ„์‚ฐํ•˜๋Š”๋ฐ ์“ฐ์ž…๋‹ˆ๋‹ค.
     private fianl Rect mTmpContainerRect = new Rect();
     private final Rect mTmpChildRect = new Rect();
     
     public CustomLayout(Context context) {
        super(context);
    }

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

    public CustomLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }
    
    /**
     * ์Šคํฌ๋กคํ•˜์ง€ ์•Š๋Š” ๋ ˆ์ด์•„์›ƒ ๋งค๋‹ˆ์ €๋Š” ์ด๊ฒƒ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
     */
    @Override
    publci boolean shouldDelayChildPressedState() {
        return false;
    }
    
    /**
     * ๋ชจ๋“  ์ž๋…€๋“ค์ด ์Šค์Šค๋กœ ์ธก์ •ํ•˜๊ณ  
     * ์ž๋…€๋“ค์ด ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๋Š” ๋ ˆ์ด์•„์›ƒ์˜ ์ธก์ •์„ ๊ณ„์‚ฐํ•˜๋„๋ก ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค.
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int HeightMeasureSpec) {
        int count = getChildCount();
        
        // ๋ฐฐ์น˜๋œ ๊ณณ์— ๋ทฐ์˜ ์™ผ์ชฝ๊ณผ ์˜ค๋ฅธ์ชฝ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ณต๊ฐ„์„ ์ถ”์ ํ•ฉ๋‹ˆ๋‹ค.
        // ๋ฉค๋ฒ„ ๋ณ€์Šˆ๋“ค์ด ํ•„์š”ํ•˜๊ณ , ๋‚˜์ค‘์— ๋ ˆ์ด์•„์›ƒ์„ ์œ„ํ•ด ์“ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
        mLeftWidth = 0;
        mRightWidth = 0;
        
        // ์ธก์ •์€ ์ด ๊ฐ’๋“ค์„ ๊ฒฐ๊ตญ์—๋Š” ๊ณ„์‚ฐํ•  ๊ฒ๋‹ˆ๋‹ค.
        int maxHeight = 0;
        int maxWidth = 0;
        int childState = 0;
        
        // ์ž๋…€๋“ค์„ ์ธก์ •ํ•˜๊ณ , ์ž๋…€์˜ ํฌ๊ธฐ๋กœ๋ถ€ํ„ฐ dimension๋“ค์„ ๊ณ„์‚ฐํ•˜๋Š” ๊ฒƒ์„
        // ๋ชจ๋“  ์ž๋…€๋“ค์—๊ฒŒ ๋ฐ˜๋ณตํ•ฉ๋‹ˆ๋‹ค.
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                // ์ž๋…€๋ฅผ ์ธก์ •ํ•ฉ๋‹ˆ๋‹ค.
                measureChildWithMargins(child, widthMeasureSpect, 0, heightMeasureSpct, 0);
                
                // layout params์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํฌ๊ธฐ ์ •๋ณด๋ฅผ ์—…๋ฐ์ดํŠธ ํ•ฉ๋‹ˆ๋‹ค.
                // ์™ผ์ชฝ์ด๋‚˜ ์˜ค๋ฅธ์ชฝ์— ๋ฐฐ์น˜ํ•˜๋„๋ก ์š”์ฒญ๋œ ์ž๋…€๋“ค์€ ์ด ์—ฌ๋ฐฑ์œผ๋กœ ๊ฐ‘๋‹ˆ๋‹ค.
                final LaoutParmas lp = (LayoutParams) child.getLayoutParmas();
                if (lp.position == LayoutParams.POSITION_LEFT) {
                    mLeftWidth += Math.max(maxWidth,
                            child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                } else if (pl.position == LayoutParams.POSITION_RIGHT) {
                    mRightWidth += Math.max(maxWidth,
                            child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                } else {
                    maxWidth = Math.max(maxWidth,
                            child.getMeasuredWidth() + lp.leftMargin + pl.rightMargin);
                }
                maxHeight = Max.max(maxHeight,
                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
                childState = combineMeasuredStates(childState, child.getMeasuredState());
            }
        }
        
        // ์ด ๋„ˆ๋น„๋Š” ๋ชจ๋“  ์•ˆ์ชฝ ์ž๋…€๋“ค์— ์—ฌ๋ฐฑ๋“ค์„ ๋”ํ•œ ๊ฒƒ์˜ ์ตœ๋Œ€๊ฐ’์ž…๋‹ˆ๋‹ค.
        maxWidth += mLeftWidth + mRightWidth;
        
        // ์ตœ์†Œ ๋†’์ด์™€ ๋„ˆ๋น„๋ฅผ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค.
        maxHeight = Max.max(maxHeight, getSuggetedMininumHeight());
        maxWidth = Max.max(maxWidth, getSuggestedMininumWidth());
        
        // ์ตœ์ข… ์ˆ˜์น˜๋ฅผ ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค.
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasrueSpect, childState),
                resolveSizeAndState(maxHeight, heightMeasureSpect,
                        childState << MEASURED_HEIGHT_STATE_SHIFT));
    }
    
    /**
     * ์ด ๋ ˆ์ด์•„์›ƒ ์•ˆ์— ๋ชจ๋“  ์ž๋…€๋“ค์„ ๋ฐฐ์น˜ํ•ฉ๋‹ˆ๋‹ค.
     */
    @Override
    protectd void onLayout(boolean changed, int left, int top, int right, int bottom) {
        final int count = getChildCount();
        
        // ๋ฐฐ์น˜ ์ž‘์—…์„ ํ•˜๋Š”๋ฐ ๋จผ ์™ผ์ชฝ๊ณผ ์˜ค๋ฅธ์ชฝ์˜ ๊ฐ€์žฅ์ž๋ฆฌ์ž…๋‹ˆ๋‹ค.
        int leftPos = getPaddingLeft();
        int rightPos = right - left - getPaddingRight();
        
        // ๊ฐ€์žฅ์ž๋ฆฌ์˜ ์ค‘๊ฐ„ ์ง€์—ญ ์•ˆ์ชฝ์ž…๋‹ˆ๋‹ค.
        final int middleLeft = leftPos + mLeftWidth;
        final int middleRight = rightPos - mRightWidth;
        
        // ๋ฐฐ์น˜ ์ž‘์—…์„ ํ•˜๋Š”๋ฐ ์ตœ์ƒ๋‹จ๊ณผ ์ตœํ•˜๋‹จ ๊ฐ€์žฅ์ž๋ฆฌ์ž…๋‹ˆ๋‹ค.
        final int parentTop = getPaddingTop();
        final int parentBottom = bottom - top - getPaddingBottom();
        
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                foma; :aupitParams lp = (LayoutParams) child.getLayoutParams();
                
                final int width = child.getMeasuredWidth();
                final int height = child.getMeasuredHeight();
                
                // ์ž๋…€๋ฅผ ๋ฐฐ์น˜ํ•˜๋Š” ํ”„๋ ˆ์ž„์„ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.
                if (lp.position == LayoutParams.POSITION_LEFT) {
                   mTmpContainerRect.left = leftPos + lp.leftMargin;
                   mTmpContainerRect.right = leftPos + width + lp.rightMargin;
                   leftPos = mTmpContainerRect.right;
                } else if (lp.position == LaoutParams.POSITION_RIGHT) {
                    mTmpContainerRect.right = rightPos - lp.rightMargin;
                    mTmpContanerRect.left = rightPos - width - lp.leftMargin;
                    rightPos = mTmpContainerRect.left;
                } else {
                    mTmpContainerRect.left = middleLeft + lp.leftMargin;
                    mTmpContainerRect.right = middleRight + lp.rightMargin;
                }
                mTmpContainerRect.top = parentTop + lp.topMargin;
                mTmpContainerRect.bottom = parentBottom - lp.bottomMargin;
                
                // ์ž๋…€์˜ gravity์™€ ํฌ๊ธฐ๋ฅผ ์‚ฌ์šฉํ•ด ์ปจํ…Œ์ด๋„ˆ ์•ˆ์˜ ์ตœ์ข… ํ”„๋ ˆ์ž„์„
                // ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.
                Gravity.apply(lp.gravity, width, height, mTmpContainerRect, mTmpChildRect);
                
                // ์ž๋…€๋ฅผ ๋ฐฐ์น˜ํ•ฉ๋‹ˆ๋‹ค.
                child.layout(mTmpChildRect.left, mTmpChildRect.top,
                        mTmpChildRect.right, mTmpChildREct.bottom);
            }
        }
    }
    
    // -----------------------------------------------------------------
    // ๊ตฌํ˜„์˜ ๋‚˜๋จธ์ง€๋Š” ์ž์‹ ๋ณ„ ์ปค์Šคํ…€ ๋ ˆ์ด์•„์›ƒ ๋งค๊ฐœ๋ณ€์ˆ˜๋“ค์„ ์œ„ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค.
    // ์ด๊ฒƒ๋“ค์ด ํ•„์š”์—†๋‹ค๋ฉด (์˜ˆ๋ฅผ ๋“ค์–ด ์ž์‹๋“ค์˜ ์œ„์น˜๋ฅผ ๊ณ ์ •์‹œํ‚ค๋Š” ๋ ˆ์ด์•„์›ƒ ๋งค๋‹ˆ์ €๋ฅผ
    // ์ž‘์„ฑํ•˜๋Š” ๊ฒฝ์šฐ) ๋‹ค ๋„˜์–ด๊ฐ€๋„ ๋ฉ๋‹ˆ๋‹ค.
    
    @Override
    public LayoutParmas generateLayotuParams(AttributeSet attrs) {
        return new CustomLayout.LayoutParmas(getContext(), attrs);
    }
    
    @Override
    protected LayoutParams generatelaoutParams(AttributeSet attrs) {
        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT0;
    }
    
    @Override
    protected LayoutParams generateDefaultLayoutParams(ViewGroup.LayoutParams p) {
        return new LayoutParam(p)
    }
    
    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof LayoutParams
    }
    
    /**
     * ์ปค์Šคํ…€ ์ž๋…€๋ณ„ ๋ ˆ์ด์•„์ˆ˜ ์ •๋ณด
     */
    public static class LayoutParams extends MarginLayoutParmas {
        /**
         * gravity๋Š” ์ด ๋ ˆ์ด์•„์›ƒ ๋งค๊ฐœ๋ณ€์ˆ˜๋“ค๊ณผ ๊ด€๋ จ๋œ ๋ทฐ์— ์ ์šฉ๋œ๋‹ค.
         */
        public int gravity = Gravity.TOP | Gravity.START;
        
        public static int POSITION_MIDDLE = 0;
        public static int POSITION_LEFT = 1;
        public static int POSITION_RIGHT = 2;
        
        public int position = POSITION_MIDDLE;
        
        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
            
            // ์ธํ”Œ๋ ˆ์ด์…˜ ๋™์•ˆ layout XML์—์„œ ๋ ˆ์ด์•„์›ƒ ๋งค๊ฐœ๋ณ€์ˆ˜ ๊ฐ’๋“ค์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
            // XML์—์„œ ๋ ˆ์ด์•„์›ƒ ํ–‰์œ„๊ฐ€ ๋ฐ”๋€Œ๋Š” ๊ฒƒ์— ์‹ ๊ฒฝ์“ฐ์ง€ ์•Š๋Š”๋‹ค๋ฉด ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
            TypedArray e = c.obtainStyledAttributes(attrs, R.styleable.CustomLaoutLP);
            gravity = a.getInt(R.styleable.CustomLayoutLP_android_layout_gravity, gravity);
            position = a.getInt(R.styleable.CustomLaoutLP_layout_position, position);
            a.recycle();
        }
        
        public LayoutParams(int width, int height) {
            super(width, height)
        }
        
        public LayoutParmas(ViewGroup.LayoutParmas source) {
            super(source);
        }
    }
 }

 

๋งŒ์•ฝ ์˜ˆ์‹œ์ฒ˜๋Ÿผ XML ๋ ˆ์ด์•„์›ƒ ์†์„ฑ๋“ค์„ ๊ตฌํ˜„ํ•˜๋Š” ์ค‘์ด๋ผ๋ฉด,  ์ด๊ฒƒ์€ res/values/attrs.xml์— ๋“ค์–ด๊ฐˆ ํ•ด๋‹นํ•˜๋Š” ์ •์˜์ž…๋‹ˆ๋‹ค. 

<declare-styleable name="CustomLayoutLP">
    <attr name="android:layout_gravity" />
    <attr name="layout_position">
        <enum name="middle" value="0" />
        <enum name="left" value="1" />
        <enum name="right" value="2" />
    </attr>
</declare-styleable>

 

์ตœ์ข…์œผ๋กœ ์ด ๋ ˆ์ด์•„์›ƒ ๋งค๋‹ˆ์ €(๊ตฌํ˜„ํ•œ ๋ทฐ๊ทธ๋ฃน)๋Š” XML ๋ ˆ์ด์•„์›ƒ์—์„œ  ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

<com.example.android.apis.view.CustomLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res/com.example.android.apis"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    <!-- ์ฒซ ๋ฒˆ์งธ ๋ทฐ๋ฅผ ์™ผ์ชฝ์— ๋†“์Šต๋‹ˆ๋‹ค. -->
    <TextView
            android:background="@drawable/filled_box"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_position="left"
            android:layout_gravity="fill_vertical|center_horizontal"
            android:text="l1"/>

    <!-- ๋‘ ๋ฒˆ์งธ ๋ทฐ๋ฅผ ์™ผ์ชฝ์— ์Œ“์Šต๋‹ˆ๋‹ค. -->
    <TextView
            android:background="@drawable/filled_box"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_position="left"
            android:layout_gravity="fill_vertical|center_horizontal"
            android:text="l2"/>

    <!-- ๋˜ ๋ทฐ๋ฅผ ์˜ค๋ฅธ์ชฝ์— ๋†“์Šต๋‹ˆ๋‹ค. -->
    <TextView
            android:background="@drawable/filled_box"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_position="right"
            android:layout_gravity="fill_vertical|center_horizontal"
            android:text="r1"/>

    <!-- ์ž๋™์œผ๋กœ ๋ทฐ๋“ค์€ ๊ฐ€์šด๋ฐ๋กœ ๊ฐ‘๋‹ˆ๋‹ค. ; fill vertical gravity๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. -->
    <TextView
            android:background="@drawable/green"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="fill_vertical|center_horizontal"
            android:text="fill-vert"/>

    <!-- ์ž๋™์œผ๋กœ ๋ทฐ๋“ค์€ ๊ฐ€์šด๋ฐ๋กœ ๊ฐ‘๋‹ˆ๋‹ค.; fill horizontal gravity๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. -->
    <TextView
            android:background="@drawable/green"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical|fill_horizontal"
            android:text="fill-horiz"/>

    <!-- ์ž๋™์œผ๋กœ ๋ทฐ๋“ค์€ ๊ฐ€์šด๋ฐ๋กœ ๊ฐ‘๋‹ˆ๋‹ค.; top-left gravity๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. -->
    <TextView
            android:background="@drawable/blue"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="top|left"
            android:text="top-left"/>

    <!-- ์ž๋™์œผ๋กœ ๋ทฐ๋“ค์€ ๊ฐ€์šด๋ฐ๋กœ ๊ฐ‘๋‹ˆ๋‹ค.; center gravity๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. -->
    <TextView
            android:background="@drawable/blue"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:text="center"/>

    <!-- ์ž๋™์œผ๋กœ ๋ทฐ๋“ค์€ ๊ฐ€์šด๋ฐ๋กœ ๊ฐ‘๋‹ˆ๋‹ค.; bottom-right gravity๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. -->
    <TextView
            android:background="@drawable/blue"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|right"
            android:text="bottom-right"/>

</com.example.android.apis.view.CustomLayout>