IT TIP

Android에서 확장 가능한 패널을 구현하는 방법은 무엇입니까?

itqueen 2020. 12. 1. 20:20
반응형

Android에서 확장 가능한 패널을 구현하는 방법은 무엇입니까?


공식 마켓 앱에서 볼 수있는 것처럼 확장 / 축소 가능한 블록을 만드는 쉬운 방법이 있습니까?

마켓 앱 스크린 샷, "더보기"버튼을 클릭하면 설명 섹션이 애니메이션으로 확장됩니다.

여기에 이미지 설명 입력

SlidingDrawer에 대해 알고 있지만 이와 같은 작업에는 적합하지 않은 것 같습니다. 오버레이에 넣어야하며 반 개방 상태를 지원하지 않습니다.

최신 정보:

여기 내 반쯤 작동하는 솔루션이 있습니다. .NET을 확장하는 사용자 지정 위젯입니다 LinearLayout. 일종의 작동하지만 collapsedHeight매개 변수 보다 작은 콘텐츠 높이와 같이 가장자리 케이스를 잘 처리하지 못합니다 . 충분히 쳐다보고, 코드를 파고 들고, 문제를 실험 해보면 고칠 수 있다고 확신합니다. 이를 피하고 기성품 공식 또는 타사 솔루션을 사용하여 시간을 절약하기를 바랬습니다. 어쨌든 여기에 코드가 있습니다.

package com.example.androidapp.widgets;

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.widget.LinearLayout;

import com.example.androidapp.R;

public class ExpandablePanel extends LinearLayout {

    private final int mHandleId;
    private final int mContentId;

    private View mHandle;
    private View mContent;

    private boolean mExpanded = true;
    private int mCollapsedHeight = 0;
    private int mContentHeight = 0;

    public ExpandablePanel(Context context) {
        this(context, null);
    }

    public ExpandablePanel(Context context, AttributeSet attrs) {
        super(context, attrs);

        TypedArray a = context.obtainStyledAttributes(attrs,
            R.styleable.ExpandablePanel, 0, 0);

        // How high the content should be in "collapsed" state
        mCollapsedHeight = (int) a.getDimension(
            R.styleable.ExpandablePanel_collapsedHeight, 0.0f);

        int handleId = a.getResourceId(R.styleable.ExpandablePanel_handle, 0);
        if (handleId == 0) {
            throw new IllegalArgumentException(
                "The handle attribute is required and must refer "
                    + "to a valid child.");
        }

        int contentId = a.getResourceId(R.styleable.ExpandablePanel_content, 0);
        if (contentId == 0) {
            throw new IllegalArgumentException(
                "The content attribute is required and must refer "
                    + "to a valid child.");
        }

        mHandleId = handleId;
        mContentId = contentId;

        a.recycle();
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        mHandle = findViewById(mHandleId);
        if (mHandle == null) {
            throw new IllegalArgumentException(
                "The handle attribute is must refer to an"
                    + " existing child.");
        }

        mContent = findViewById(mContentId);
        if (mContent == null) {
            throw new IllegalArgumentException(
                "The content attribute is must refer to an"
                    + " existing child.");
        }

        mHandle.setOnClickListener(new PanelToggler());
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mContentHeight == 0) {
            // First, measure how high content wants to be
            mContent.measure(widthMeasureSpec, MeasureSpec.UNSPECIFIED);
            mContentHeight = mContent.getMeasuredHeight();
        }

        // Then let the usual thing happen
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    private class PanelToggler implements OnClickListener {
        public void onClick(View v) {
            Animation a;
            if (mExpanded) {
                a = new ExpandAnimation(mContentHeight, mCollapsedHeight);
            } else {
                a = new ExpandAnimation(mCollapsedHeight, mContentHeight);
            }
            a.setDuration(500);
            mContent.startAnimation(a);
            mExpanded = !mExpanded;
        }
    }

    private class ExpandAnimation extends Animation {
        private final int mStartHeight;
        private final int mDeltaHeight;

        public ExpandAnimation(int startHeight, int endHeight) {
            mStartHeight = startHeight;
            mDeltaHeight = endHeight - startHeight;
        }

        @Override
        protected void applyTransformation(float interpolatedTime,
            Transformation t) {
            android.view.ViewGroup.LayoutParams lp = mContent.getLayoutParams();
            lp.height = (int) (mStartHeight + mDeltaHeight * interpolatedTime);
            mContent.setLayoutParams(lp);
        }

        @Override
        public boolean willChangeBounds() {
            // TODO Auto-generated method stub
            return true;
        }
    }
}

여기 있습니다 res/values/attrs.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <declare-styleable name="ExpandablePanel">
    <attr name="handle" format="reference" />
    <attr name="content" format="reference" />
    <attr name="collapsedHeight" format="dimension" />
  </declare-styleable>
</resources>

레이아웃에서 사용하는 방법은 다음과 같습니다.

<com.example.androidapp.widgets.ExpandablePanel
    android:orientation="vertical"
    android:layout_height="wrap_content"
    android:layout_width="fill_parent"
    example:handle="@+id/expand"
    example:content="@+id/value"
    example:collapsedHeight="50dip">
    <TextView
        android:id="@id/value"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:maxHeight="50dip"
        />
    <Button
        android:id="@id/expand"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="More" />
</com.example.androidapp.widgets.ExpandablePanel>

감사합니다 OP! 관심있는 사람을 위해 OP의 솔루션을 가져 와서 조금 수정했습니다.

  • 오버플로가있는 경우에만 핸들이 표시됩니다.
  • 'animationDuration'속성을 통해 애니메이션 기간을 지정하는 기능 추가
  • onExpand 및 onCollapse에서 시작되는 이벤트 리스너를 연결하는 기능이 추가되었습니다 (예 : "More"버튼의 텍스트를 "Less"로 변경하는 데 유용합니다.
  • 기본적으로 축소됨
  • 콘텐츠는 프로그래밍 방식으로 수정 가능 (속성과 동일)

업데이트 된 코드는 다음과 같습니다.

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.widget.LinearLayout;

public class ExpandablePanel extends LinearLayout {

    private final int mHandleId;
    private final int mContentId;

    private View mHandle;
    private View mContent;

    private boolean mExpanded = false;
    private int mCollapsedHeight = 0;
    private int mContentHeight = 0;
    private int mAnimationDuration = 0;

    private OnExpandListener mListener;

    public ExpandablePanel(Context context) {
        this(context, null);
    }

    public ExpandablePanel(Context context, AttributeSet attrs) {
        super(context, attrs);
        mListener = new DefaultOnExpandListener();

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ExpandablePanel, 0, 0);

        // How high the content should be in "collapsed" state
        mCollapsedHeight = (int) a.getDimension(R.styleable.ExpandablePanel_collapsedHeight, 0.0f);

        // How long the animation should take
        mAnimationDuration = a.getInteger(R.styleable.ExpandablePanel_animationDuration, 500);

        int handleId = a.getResourceId(R.styleable.ExpandablePanel_handle, 0);
        if (handleId == 0) {
            throw new IllegalArgumentException(
                "The handle attribute is required and must refer "
                    + "to a valid child.");
        }

        int contentId = a.getResourceId(R.styleable.ExpandablePanel_content, 0);
        if (contentId == 0) {
            throw new IllegalArgumentException("The content attribute is required and must refer to a valid child.");
        }

        mHandleId = handleId;
        mContentId = contentId;

        a.recycle();
    }

    public void setOnExpandListener(OnExpandListener listener) {
        mListener = listener; 
    }

    public void setCollapsedHeight(int collapsedHeight) {
        mCollapsedHeight = collapsedHeight;
    }

    public void setAnimationDuration(int animationDuration) {
        mAnimationDuration = animationDuration;
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        mHandle = findViewById(mHandleId);
        if (mHandle == null) {
            throw new IllegalArgumentException(
                "The handle attribute is must refer to an"
                    + " existing child.");
        }

        mContent = findViewById(mContentId);
        if (mContent == null) {
            throw new IllegalArgumentException(
                "The content attribute must refer to an"
                    + " existing child.");
        }

        android.view.ViewGroup.LayoutParams lp = mContent.getLayoutParams();
        lp.height = mCollapsedHeight;
        mContent.setLayoutParams(lp);

        mHandle.setOnClickListener(new PanelToggler());
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // First, measure how high content wants to be
        mContent.measure(widthMeasureSpec, MeasureSpec.UNSPECIFIED);
        mContentHeight = mContent.getMeasuredHeight();

        if (mContentHeight < mCollapsedHeight) {
            mHandle.setVisibility(View.GONE);
        } else {
            mHandle.setVisibility(View.VISIBLE);
        }

        // Then let the usual thing happen
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    private class PanelToggler implements OnClickListener {
        public void onClick(View v) {
            Animation a;
            if (mExpanded) {
                a = new ExpandAnimation(mContentHeight, mCollapsedHeight);
                mListener.onCollapse(mHandle, mContent);
            } else {
                a = new ExpandAnimation(mCollapsedHeight, mContentHeight);
                mListener.onExpand(mHandle, mContent);
            }
            a.setDuration(mAnimationDuration);
            mContent.startAnimation(a);
            mExpanded = !mExpanded;
        }
    }

    private class ExpandAnimation extends Animation {
        private final int mStartHeight;
        private final int mDeltaHeight;

        public ExpandAnimation(int startHeight, int endHeight) {
            mStartHeight = startHeight;
            mDeltaHeight = endHeight - startHeight;
        }

        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            android.view.ViewGroup.LayoutParams lp = mContent.getLayoutParams();
            lp.height = (int) (mStartHeight + mDeltaHeight * interpolatedTime);
            mContent.setLayoutParams(lp);
        }

        @Override
        public boolean willChangeBounds() {
            return true;
        }
    }

    public interface OnExpandListener {
        public void onExpand(View handle, View content); 
        public void onCollapse(View handle, View content);
    }

    private class DefaultOnExpandListener implements OnExpandListener {
        public void onCollapse(View handle, View content) {}
        public void onExpand(View handle, View content) {}
    }
}

그리고 attrs.xml을 잊지 마세요.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="ExpandablePanel">
        <attr name="handle" format="reference" />
        <attr name="content" format="reference" />
        <attr name="collapsedHeight" format="dimension"/>
        <attr name="animationDuration" format="integer"/>
    </declare-styleable>
</resources>

위의 XML 레이아웃에 대한 OP의 예제 사용을 참조하십시오. 다음은 청취자를위한 예입니다.

// Set expandable panel listener
ExpandablePanel panel = (ExpandablePanel)view.findViewById(R.id.foo);
panel.setOnExpandListener(new ExpandablePanel.OnExpandListener() {
    public void onCollapse(View handle, View content) {
        Button btn = (Button)handle;
        btn.setText("More");
    }
    public void onExpand(View handle, View content) {
        Button btn = (Button)handle;
        btn.setText("Less");
    }
});

나는 이것이 오래된 질문이라는 것을 알고 있지만 관심있는 사람들을 위해 ahal과 Pēteris Caune이 한 일에 추가했습니다.

추가

  1. 가로보기 및 추가 버튼을 포함하는 레이아웃 포함 (Pēteris Caune의 질문에서 이미지 참조)
  2. 오버플로가 없으면 버튼 만 제거되는 대신 레이아웃이 제거됩니다.
  3. 버튼 상태에 따라 숨겨진 텍스트가 표시되거나 숨겨집니다.

업데이트 된 코드

ExpandablePanel 클래스

package com.example.myandroidhustles;
import com.example.myandroidhustles.R;

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.widget.LinearLayout;

public class ExpandablePanel extends LinearLayout {

    private final int mHandleId;
    private final int mContentId;
    private final int mViewGroupId;

    private final boolean isViewGroup;

    private View mHandle;
    private View mContent;
    private ViewGroup viewGroup;

    private boolean mExpanded = false;
    private int mCollapsedHeight = 0;
    private int mContentHeight = 0;
    private int mAnimationDuration = 0;

    private OnExpandListener mListener;

    public ExpandablePanel(Context context) {
        this(context, null);
    }

    public ExpandablePanel(Context context, AttributeSet attrs) {
        super(context, attrs);
        mListener = new DefaultOnExpandListener();

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ExpandablePanel, 0, 0);

        // How high the content should be in "collapsed" state
        mCollapsedHeight = (int) a.getDimension(R.styleable.ExpandablePanel_collapsedHeight, 0.0f);

        // How long the animation should take
        mAnimationDuration = a.getInteger(R.styleable.ExpandablePanel_animationDuration, 500);

        int handleId = a.getResourceId(R.styleable.ExpandablePanel_handle, 0);
        if (handleId == 0) {
            throw new IllegalArgumentException(
                "The handle attribute is required and must refer "
                    + "to a valid child.");
        }

        int contentId = a.getResourceId(R.styleable.ExpandablePanel_content, 0);
        if (contentId == 0) {
            throw new IllegalArgumentException("The content attribute is required and must refer to a valid child.");
        }

        int isViewGroupId = a.getResourceId(R.styleable.ExpandablePanel_isviewgroup, 0);
        int viewGroupId = a.getResourceId(R.styleable.ExpandablePanel_viewgroup, 0);
//        isViewGroup = findViewById(isViewGroupId);
        isViewGroup = a.getBoolean(R.styleable.ExpandablePanel_isviewgroup, false);
        if (isViewGroup) {
            mViewGroupId = viewGroupId;
        }
        else {
            mViewGroupId = 0;
        }

        mHandleId = handleId;
        mContentId = contentId;        

        a.recycle();
    }

    public void setOnExpandListener(OnExpandListener listener) {
        mListener = listener; 
    }

    public void setCollapsedHeight(int collapsedHeight) {
        mCollapsedHeight = collapsedHeight;
    }

    public void setAnimationDuration(int animationDuration) {
        mAnimationDuration = animationDuration;
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        mHandle = findViewById(mHandleId);        
        if (mHandle == null) {
            throw new IllegalArgumentException(
                "The handle attribute is must refer to an"
                    + " existing child.");
        }
        if(mViewGroupId != 0) {
            viewGroup = (ViewGroup) findViewById(mViewGroupId);
        }


        mContent = findViewById(mContentId);
        if (mContent == null) {
            throw new IllegalArgumentException(
                "The content attribute must refer to an"
                    + " existing child.");
        }

        android.view.ViewGroup.LayoutParams lp = mContent.getLayoutParams();
        lp.height = mCollapsedHeight;
        mContent.setLayoutParams(lp);

        mHandle.setOnClickListener(new PanelToggler());
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // First, measure how high content wants to be
        mContent.measure(widthMeasureSpec, MeasureSpec.UNSPECIFIED);
        mContentHeight = mContent.getMeasuredHeight();

        if (mContentHeight < mCollapsedHeight) {
            viewGroup.setVisibility(View.GONE);
//            mHandle.setVisibility(View.GONE);

        } else {
            viewGroup.setVisibility(View.VISIBLE);
//            mHandle.setVisibility(View.VISIBLE);
        }

        // Then let the usual thing happen
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    private class PanelToggler implements OnClickListener {
        public void onClick(View v) {
            Animation a;
            if (mExpanded) {
                a = new ExpandAnimation(mContentHeight, mCollapsedHeight);
                mListener.onCollapse(mHandle, mContent);
            } else {
                a = new ExpandAnimation(mCollapsedHeight, mContentHeight);
                mListener.onExpand(mHandle, mContent);
            }
            a.setDuration(mAnimationDuration);
            if(mContent.getLayoutParams().height == 0) //Need to do this or else the animation will not play if the height is 0
           {
            android.view.ViewGroup.LayoutParams lp = mContent.getLayoutParams();
            lp.height = 1;
            mContent.setLayoutParams(lp);
            mContent.requestLayout();
          }
            mContent.startAnimation(a);
            mExpanded = !mExpanded;
        }
    }

    private class ExpandAnimation extends Animation {
        private final int mStartHeight;
        private final int mDeltaHeight;

        public ExpandAnimation(int startHeight, int endHeight) {
            mStartHeight = startHeight;
            mDeltaHeight = endHeight - startHeight;
        }

        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            android.view.ViewGroup.LayoutParams lp = mContent.getLayoutParams();
            lp.height = (int) (mStartHeight + mDeltaHeight * interpolatedTime);
            mContent.setLayoutParams(lp);
        }

        @Override
        public boolean willChangeBounds() {
            return true;
        }
    }

    public interface OnExpandListener {
        public void onExpand(View handle, View content); 
        public void onCollapse(View handle, View content);
    }

    private class DefaultOnExpandListener implements OnExpandListener {
        public void onCollapse(View handle, View content) {}
        public void onExpand(View handle, View content) {}
    }
}

attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="ExpandablePanel">
        <attr name="handle" format="reference" />
        <attr name="content" format="reference" />
        <attr name="viewgroup" format="reference"/>
        <attr name="isviewgroup" format="boolean"/>
        <attr name="collapsedHeight" format="dimension"/>
        <attr name="animationDuration" format="integer"/>
    </declare-styleable>
</resources>

레이아웃 : tryExpandablePanel.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:example="http://schemas.android.com/apk/res/com.example.myandroidhustles"
    android:layout_width="fill_parent"
    android:layout_height="match_parent" >

    <com.example.myandroidhustles.ExpandablePanel
        android:id="@+id/expandablePanel"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="vertical"
        example:collapsedHeight="50dip"
        example:content="@+id/value"
        example:handle="@+id/expand"
        example:isviewgroup="true"
        example:viewgroup="@+id/expandL" >

        <TextView
            android:id="@+id/value"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:maxHeight="100dip" />

        <LinearLayout
            android:id="@+id/expandL"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:paddingLeft="10dp"
            android:weightSum="100" >

            <View
                android:id="@+id/view"
                android:layout_width="fill_parent"
                android:layout_height="1dp"
                android:layout_gravity="center_vertical|left"
                android:layout_weight="30"
                android:background="@android:color/darker_gray" />

            <Button
                android:id="@+id/expand"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="right"
                android:layout_weight="70"
                android:text="More" />
        </LinearLayout>
    </com.example.myandroidhustles.ExpandablePanel>

</LinearLayout>

구현 : ExpandablePanelImplementation 클래스

package com.example.myandroidhustles;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;


public class ExpandablePanelImplementation extends Activity {
    ExpandablePanel panel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.tryexpandable);
        TextView text;
        text = (TextView)findViewById(R.id.value);

            text.setText("ksaflfsklafjsfj sdfjklds fj asklfjklasfjskladf fjslkafjf" +
                    "asfkdaslfjsf;sjdaflkadsjflkdsajfkldsajflkdsanfvsjvfdskljflkdnjdsadf" +
                    "askfvdsklfjvsdlkfjdsklvdkjkdsadsj;lkasjdfklvsddsjkdsljskldfj");


      panel = (ExpandablePanel)findViewById(R.id.expandablePanel);

      panel.setOnExpandListener(new ExpandablePanel.OnExpandListener() {
          public void onCollapse(View handle, View content) {
              Button btn = (Button)handle;
              btn.setText("More");

              panel.setCollapsedHeight(100);
          }
          public void onExpand(View handle, View content) {
              Button btn = (Button)handle;
              panel.setCollapsedHeight(50);
              btn.setText("Less");
          }
      });


    }

}

ScrollView클릭 할 수없고 초점을 맞출 수없는 크기를 설정해 보셨습니까 ? 그런 다음 확장 할 때 더 크게 애니메이션 할 수 있습니다.


큰 확장 아할. 내가 찾은 버그를 수정하기 위해 코드를 약간 수정했습니다.

a.setDuration(mAnimationDuration);PanelToggler에서 128 행 주위에 추가했습니다.

if(mContent.getLayoutParams().height == 0) //Need to do this or else the animation will not play if the height is 0
{
    android.view.ViewGroup.LayoutParams lp = mContent.getLayoutParams();
    lp.height = 1;
    mContent.setLayoutParams(lp);
    mContent.requestLayout();
}

콘텐츠 높이가 0이면 애니메이션이 재생되지 않으므로 애니메이션 전에 1로 설정해야합니다.

참고 URL : https://stackoverflow.com/questions/5165682/how-to-implement-expandable-panels-in-android

반응형