Intereting Posts
Ресурсы для drawable-xlarge-mdpi И drawable-sw600dp-mdpi без дублирования Android Eclipse не может отлаживать Как создавать и создавать приложения с помощью Mono для Android и MonoDevelop на Linux? UnsupportedException из Canvas.setBitmap (Bitmap) Обнаружение начала прокрутки и завершения прокрутки в режиме recyclerview Командная команда Run-android Невозможно создать обработчик внутри потока, который не вызвал Looper.prepare () Полоса хода с обеих сторон в андроиде Скрыть уведомление об обслуживании переднего плана, пока активность видна Размер кучи Android на разных телефонах / устройствах и версиях ОС Просмотр анимации позади другого макета Получите список приложений, способных обрабатывать намерение SEND для отображения в представлении (а не всплывающем диалоговом окне) Как не писать спагетти-код / ​​огромные действия с Android? Android ExpandableListView не расширяется / не изменяется Невозможно создать экземпляр приемника в SMS-сообщении BroadcastReceiver

Как реализовать расширяемые панели в Android?

Есть ли простой способ создания расширяемых / сбрасываемых блоков, например, в официальном приложении на рынке?

Снимок экрана приложения Market, когда вы нажимаете кнопку «Дополнительно», раздел описания расширяется с анимацией:

Введите описание изображения здесь

Я знаю SlidingDrawer, но, похоже, он не подходит для таких вещей – он должен быть помещен в оверлей и не поддерживает полуоткрытые состояния.

Обновить:

Вот мое полуприводное решение. Это настраиваемый виджет, расширяющий 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> 

Solutions Collecting From Web of "Как реализовать расширяемые панели в Android?"

Большое спасибо OP! Для всех, кого это интересует, я принял решение OP и немного уточнил его.

  • Ручка отображает только в случае переполнения
  • Добавлена ​​возможность указывать продолжительность анимации через атрибут 'animationDuration'
  • Добавлена ​​возможность подключать прослушиватели событий, которые запускаются onExpand и onCollapse (это полезно, например, для изменения текста кнопки «Дополнительно» на «Меньше»,
  • Постепенно по умолчанию
  • Содержимое может быть изменено программно (то же, что и атрибуты)

Вот обновленный код:

 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> 

См. Пример использования примера OP для XML-макета выше. Вот пример для слушателей:

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

Я знаю, что это старый вопрос, но для тех, кто заинтересован, я сделал дополнения к тому, что делали ахал и Петерис Каун.

дополнения

  1. Включен макет, который содержит горизонтальный вид и большую кнопку (см. Изображение с вопроса Петериса Кауна)
  2. Макет, а не только кнопка удаляется, когда нет переполнения
  3. Скрытый текст отображается или скрывается в зависимости от состояния кнопки

Обновленный код

ExpandablePanel Class

 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> 1 <?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> 

Реализация: расширяемый класс PanelImplementation

 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 с заданным размером, который вы делаете не кликабельным и не настраиваемым? Затем, когда вы расширяете его, вы можете просто оживить его.

Отличное расширение агаля. Я немного изменил код, чтобы исправить найденную ошибку.

Я добавил это вокруг строки 128 после a.setDuration(mAnimationDuration); В PanelToggler

 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 перед анимацией.