showing - viewpager android github
MediciĆ³n de un ViewPager (6)
Tengo un ViewGroup personalizado que tiene un ViewPager
secundario. El ViewPager es alimentado por un PagerAdapter
que proporciona un LinearLayout
al ViewPager
que tiene LayoutParams
de WRAP_CONTENT
en alto y ancho.
La vista se muestra correctamente, pero cuando se llama al método child.measure()
en el ViewPager, no devuelve las dimensiones reales de LinearLayout, pero parece llenar todo el espacio restante.
¿Alguna idea de por qué sucede esto y cómo enmendarlo?
Con la referencia de las soluciones anteriores, se agregaron algunas declaraciones más para obtener la altura máxima del hijo del buscapersonas.
Consulte el siguiente código.
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// super has to be called in the beginning so the child views can be
// initialized.
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (getChildCount() <= 0)
return;
// Check if the selected layout_height mode is set to wrap_content
// (represented by the AT_MOST constraint).
boolean wrapHeight = MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST;
int width = getMeasuredWidth();
int childCount = getChildCount();
int height = getChildAt(0).getMeasuredHeight();
int fragmentHeight = 0;
for (int index = 0; index < childCount; index++) {
View firstChild = getChildAt(index);
// Initially set the height to that of the first child - the
// PagerTitleStrip (since we always know that it won''t be 0).
height = firstChild.getMeasuredHeight() > height ? firstChild.getMeasuredHeight() : height;
int fHeight = measureFragment(((Fragment) getAdapter().instantiateItem(this, index)).getView());
fragmentHeight = fHeight > fragmentHeight ? fHeight : fragmentHeight;
}
if (wrapHeight) {
// Keep the current measured width.
widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
}
// Just add the height of the fragment:
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height + fragmentHeight, MeasureSpec.EXACTLY);
// super has to be called again so the new specs are treated as
// exact measurements.
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
No estaba muy contento con la respuesta aceptada (ni con la solución de pre-inflar todas las vistas en los comentarios), así que ViewPager
un ViewPager
que toma su altura desde el primer niño disponible. Lo hace haciendo un segundo pase de medición, lo que le permite robar la altura del primer niño.
Una mejor solución sería crear una nueva clase dentro del paquete android.support.v4.view
que implemente una mejor versión de onMeasure
(con acceso a métodos visibles desde el paquete como populate()
)
Por el momento, sin embargo, la solución a continuación me queda bien.
public class HeightWrappingViewPager extends ViewPager {
public HeightWrappingViewPager(Context context) {
super(context);
}
public HeightWrappingViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
boolean wrapHeight = MeasureSpec.getMode(heightMeasureSpec)
== MeasureSpec.AT_MOST;
if(wrapHeight) {
/**
* The first super.onMeasure call made the pager take up all the
* available height. Since we really wanted to wrap it, we need
* to remeasure it. Luckily, after that call the first child is
* now available. So, we take the height from it.
*/
int width = getMeasuredWidth(), height = getMeasuredHeight();
// Use the previously measured width but simplify the calculations
widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
/* If the pager actually has any children, take the first child''s
* height and call that our own */
if(getChildCount() > 0) {
View firstChild = getChildAt(0);
/* The child was previously measured with exactly the full height.
* Allow it to wrap this time around. */
firstChild.measure(widthMeasureSpec,
MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
height = firstChild.getMeasuredHeight();
}
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
}
Observando los aspectos internos de la clase ViewPager en el jar de compatibilidad:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
// For simple implementation, or internal size is always 0.
// We depend on the container to specify the layout size of
// our view. We can''t really know what it is since we will be
// adding and removing different arbitrary views and do not
// want the layout to change as this happens.
setMeasuredDimension(getDefaultSize(0, widthMeasureSpec), getDefaultSize(0, heightMeasureSpec));
...
}
Parecería que la implementación de ViewPager no mide las vistas secundarias, sino que simplemente establece que ViewPager sea una vista estándar basada en lo que está pasando el padre. Cuando se pasa wrap_content, ya que la página de visualización no mide realmente el contenido que toma Hasta el área completa disponible.
Mi recomendación sería establecer un tamaño estático en su ViewPager según el tamaño de las vistas de su hijo. Si esto es imposible (por ejemplo, las vistas secundarias pueden variar) deberá elegir un tamaño máximo y ocuparse del espacio adicional en algunas vistas O extender el ViewPager y proporcionar una Medida que mida a los secundarios. Uno de los problemas con los que se encontrará es que el buscapersonas de la vista fue diseñado para que no varíe en el ancho a medida que se muestran las diferentes vistas, por lo que probablemente se verá obligado a elegir un tamaño y permanecer con él.
Si establece la etiqueta (posición) en el elemento de instancia de su PageAdapter:
@Override
public Object instantiateItem(ViewGroup collection, int page) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = (View) inflater.inflate(R.layout.page_item , null);
view.setTag(page);
luego puede recuperar la vista (página del adaptador) con un OnPageChangeListener, medirla y cambiar el tamaño de su ViewPager:
private ViewPager pager;
@Override
protected void onCreate(Bundle savedInstanceState) {
pager = findViewById(R.id.viewpager);
pager.setOnPageChangeListener(new SimpleOnPageChangeListener() {
@Override
public void onPageSelected(int position) {
resizePager(position);
}
});
public void resizePager(int position) {
View view = pager.findViewWithTag(position);
if (view == null)
return;
view.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
//The layout params must match the parent of the ViewPager
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(width , height);
pager.setLayoutParams(params);
}
}
Siguiendo el ejemplo anterior, descubrí que medir la altura de las vistas secundarias no siempre arroja resultados precisos. La solución es medir la altura de cualquier vista estática (definida en el xml) y luego agregar la altura del fragmento que se crea dinámicamente en la parte inferior. En mi caso, el elemento estático fue el PagerTitleStrip, que también tuve que Anular para habilitar el uso de match_parent para el ancho en modo horizontal.
Así que aquí está mi opinión sobre el código de Delyan:
public class WrappingViewPager extends ViewPager {
public WrappingViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// super has to be called in the beginning so the child views can be
// initialized.
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (getChildCount() <= 0)
return;
// Check if the selected layout_height mode is set to wrap_content
// (represented by the AT_MOST constraint).
boolean wrapHeight = MeasureSpec.getMode(heightMeasureSpec)
== MeasureSpec.AT_MOST;
int width = getMeasuredWidth();
View firstChild = getChildAt(0);
// Initially set the height to that of the first child - the
// PagerTitleStrip (since we always know that it won''t be 0).
int height = firstChild.getMeasuredHeight();
if (wrapHeight) {
// Keep the current measured width.
widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
}
int fragmentHeight = 0;
fragmentHeight = measureFragment(((Fragment) getAdapter().instantiateItem(this, getCurrentItem())).getView());
// Just add the height of the fragment:
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height + fragmentHeight,
MeasureSpec.EXACTLY);
// super has to be called again so the new specs are treated as
// exact measurements.
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
public int measureFragment(View view) {
if (view == null)
return 0;
view.measure(0, 0);
return view.getMeasuredHeight();
}}
Y el PagerTitleStrip personalizado:
public class MatchingPagerTitleStrip extends android.support.v4.view.PagerTitleStrip {
public MatchingPagerTitleStrip(Context arg0, AttributeSet arg1) {
super(arg0, arg1);
}
@Override
protected void onMeasure(int arg0, int arg1) {
int size = MeasureSpec.getSize(arg0);
int newWidthSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
super.onMeasure(newWidthSpec, arg1);
}}
¡Aclamaciones!
mejor cambio
height = firstChild.getMeasuredHeight();
a
height = firstChild.getMeasuredHeight() + getPaddingTop() + getPaddingBottom();