SwipeRefreshLayout setRefreshing () no muestra el indicador inicialmente

144

Tengo un diseño muy simple, pero cuando llamo setRefreshing(true)a onActivityCreated()mi fragmento, no se muestra inicialmente.

Solo se muestra cuando hago una extracción para actualizar. ¿Alguna idea de por qué no aparece inicialmente?

Fragmento xml:

<android.support.v4.widget.SwipeRefreshLayout
    android:id="@+id/swipe_container"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

        </RelativeLayout>


    </ScrollView>
</android.support.v4.widget.SwipeRefreshLayout>

Código de fragmento:

public static class LinkDetailsFragment extends BaseFragment implements SwipeRefreshLayout.OnRefreshListener {

    @InjectView(R.id.swipe_container)
    SwipeRefreshLayout mSwipeContainer;

    public static LinkDetailsFragment newInstance(String subreddit, String linkId) {
        Bundle args = new Bundle();
        args.putString(EXTRA_SUBREDDIT, subreddit);
        args.putString(EXTRA_LINK_ID, linkId);

        LinkDetailsFragment fragment = new LinkDetailsFragment();
        fragment.setArguments(args);

        return fragment;
    }

    public LinkDetailsFragment() {
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        mSwipeContainer.setOnRefreshListener(this);
        mSwipeContainer.setColorScheme(android.R.color.holo_blue_bright,
                android.R.color.holo_green_light,
                android.R.color.holo_orange_light,
                android.R.color.holo_red_light);
        mSwipeContainer.setRefreshing(true);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        final View rootView = inflater.inflate(R.layout.fragment_link_details, container, false);
        ButterKnife.inject(this, rootView);
        return rootView;
    }

    @Override
    public void onRefresh() {
        // refresh
    }
}
Ninja atronador
fuente
Que versión usas?
Ahmed Hegazy
compilar "com.android.support:appcompat-v7:21.0.0"
thunderousNinja el
Confirmo que este problema ocurrió conmigo desde esa versión en curso. Las versiones anteriores no tienen ningún problema con eso. Publicaré la solución si consigo alguna.
Ahmed Hegazy
Permítanme probar una versión anterior
atronador
Tampoco funciona en v20 para mí. ¿En qué versión está trabajando para ti?
ThunderousNinja

Respuestas:

307

Ante el mismo problema. Mi solución -

mSwipeRefreshLayout.post(new Runnable() {
    @Override
    public void run() {
        mSwipeRefreshLayout.setRefreshing(true);
    }
});
Volodymyr Baydalka
fuente
1
Funciona bien pero no sé por qué necesitamos hacer esto en lugar de mSwipeRefreshLayout.setRefreshing (verdadero);
Cocorico
1
Esa no es una buena solución / solución alternativa. Si el usuario está en modo avión, su refreshLayout comenzará a actualizarse en su propio hilo después de comenzar la solicitud de red y recibir su respuesta (falla en este caso). Al manejarlo para detener la actualización, no funcionaría ya que aún no comenzó. En otras palabras, refreshLayout comenzará a actualizarse después de que haya recibido su respuesta. Buena suerte deteniéndolo
Samer
1
Como recuerdo, las "publicaciones" se sincronizan y se ejecutan en el orden de adición. Para que pueda agregar otra publicación para detenerlo.
Volodymyr Baydalka
2
Quizás parece más simple en la implementación, pero no es agradable. La solución @ niks.stack que se muestra a continuación es mejor, ya que no requiere ningún cambio en el código para usarla, por lo que cuando finalmente se solucione este error en support lib, simplemente cambie de nuevo a support lib SwipeRefreshLayout
Marcin Orlowski
100

Ver la respuesta de Volodymyr Baydalka en su lugar.

Estas son las viejas soluciones alternativas.

Eso solía funcionar en una versión anterior de la android.support.v4, pero desde la versión 21.0.0 en curso no funciona y todavía existe con el android.support.v4:21.0.3lanzamiento del 10 al 12 de diciembre de 2014 y esta es la razón.

El indicador SwipeRefreshLayout no aparece cuando setRefreshing(true)se llama antes deSwipeRefreshLayout.onMeasure()

Solución alterna:

llamando setProgressViewOffset()al SwipeRefreshLayoutque invalida la vista circular del diseño haciendo SwipeRefreshLayout.onMeasure()que se llame inmediatamente.

mSwipeRefreshLayout.setProgressViewOffset(false, 0,
                (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 24, getResources().getDisplayMetrics()));
mSwipeRefreshLayout.setRefreshing(true);

ACTUALIZAR Mejor solución

Debido a que la barra de acción podría adelgazarse cuando cambia la orientación o si ha establecido el tamaño de la barra de acción manualmente. Establecemos el desplazamiento en píxeles desde la parte superior de esta vista en la que la flecha de progreso debe reiniciarse después de un gesto de deslizamiento exitoso al tamaño actual de la barra de acción.

TypedValue typed_value = new TypedValue();
getActivity().getTheme().resolveAttribute(android.support.v7.appcompat.R.attr.actionBarSize, typed_value, true);
mSwipeRefreshLayout.setProgressViewOffset(false, 0, getResources().getDimensionPixelSize(typed_value.resourceId));

ACTUALIZACIÓN Nov 20 2014

Si no es muy importante para su aplicación mostrar SwipeRefreshLayout una vez que se inicia la vista. Puede publicarlo en un momento en el futuro mediante el uso de controladores o cualquier cosa que desee.

como ejemplo.

handler.postDelayed(new Runnable() {

    @Override
    public void run() {
        mSwipeRefreshLayout.setRefreshing(true);
    }
}, 1000);

o como mencionó la respuesta de Volodymyr Baydalka.

Aquí está el problema en el rastreador de problemas de Android. Vota a favor para mostrarles que necesitamos que se solucione.

Ahmed Hegazy
fuente
¿Hiciste muchas pruebas con el delay time? Estoy usando 500en mi Galaxy S4. No estoy seguro de si esto sería un problema en otro dispositivo.
theblang
No hice muchas pruebas con el tiempo de retraso, pero creo que 500estaría bien. Lo he probado en emulator. Solo quería estar safecon los 1000milisegundos
Ahmed Hegazy
3
No hay necesidad de publicar retrasado. solo puedes publicar. publicar sin demora solo significa "hacer esto una vez que haya terminado con lo que está haciendo ahora". y lo que está haciendo ahora es medir y diseñar su interfaz de usuario.
Oren
47

Mi solución es anular SwipeRefreshLayout:

public class MySwipeRefreshLayout extends SwipeRefreshLayout {

    private boolean mMeasured = false;
    private boolean mPreMeasureRefreshing = false;

    public MySwipeRefreshLayout(final Context context) {
        super(context);
    }

    public MySwipeRefreshLayout(final Context context, final AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (!mMeasured) {
            mMeasured = true;
            setRefreshing(mPreMeasureRefreshing);
        }
    }

    @Override
    public void setRefreshing(final boolean refreshing) {
        if (mMeasured) {
            super.setRefreshing(refreshing);
        } else {
            mPreMeasureRefreshing = refreshing;
        }
    }
}
nikita.zhelonkin
fuente
3
¡La ventaja de su solución es mantener intacta la vista offset! De esta forma continuamos de conformidad con el patrón de diseño especificado aquí: google.com/design/spec/patterns/… . ¡Gracias!
Igor de Lorenzi
¿Puedes explicar cómo funciona esto? Está funcionando pero no entiendo el funcionamiento interno. ¿Me estoy perdiendo algo trivial?
Sree
setRefreshing funciona solo después de la llamada onMeasure, por lo que almacenamos el indicador de actualización local, y en la primera llamada onMeasure lo aplicamos
nikita.zhelonkin
55
Me encanta esta solución porque significa que el código que llama a SwipeRefreshLayout puede ser exactamente como lo deseamos, sin complicaciones. Básicamente solucionó el error en SwipeRefreshLayout. SwipeRefreshLayout realmente debería implementarse de esta manera.
DataGraham
Puede que esta respuesta no parezca tan sencilla, pero soluciona el problema muy bien a través de muchas versiones de la biblioteca de soporte (en mi caso es 23.1.1). De acuerdo con el boleto, este problema no se soluciona @ 23.2. code.google.com/p/android/issues/detail?id=77712
Robert
20
mRefreshLayout.getViewTreeObserver()
                .addOnGlobalLayoutListener(
                        new ViewTreeObserver.OnGlobalLayoutListener() {
                            @Override
                            public void onGlobalLayout() {
                                mRefreshLayout
                                        .getViewTreeObserver()
                                        .removeGlobalOnLayoutListener(this);
                                mRefreshLayout.setRefreshing(true);
                            }
                        });
baoyz
fuente
3
Esta respuesta es la única que no es un hack por lo que debe ser aceptado
Heinrich
1
Solo una pequeña cosa: .removeGlobalOnLayoutListener debería ser .removeOnGlobalLayoutListener
mkuech
1
@mkeuch depende de la API de su orientación. Si está apuntando bajo API16, debe hacer una verificación de versión de API y usar ambos.
Marko
Esta me gusta un poco más que la respuesta más votada porque está más claro por qué se usa.
Marcel Bro
Debo corregirme, esta solución NO siempre funciona para mí. En algunos casos, las llamadas setRefreshing(false)incluidas onGlobalLayoutListener()no descartan el indicador de carga.
Marcel Bro
4

Según la respuesta de Volodymyr Baydalka , esta es solo una pequeña idea que lo ayudará a mantener limpio el código. Merece una publicación, creo: podrá revertir fácilmente el comportamiento de la publicación a la llamada al método directo, una vez que se solucione el error.

Escribe una clase de utilidad como:

public class Utils
{
    private Utils()
    {
    }

    public static void setRefreshing(final SwipeRefreshLayout swipeRefreshLayout, final boolean isRefreshing)
    {
        // From Guava, or write your own checking code
        checkNonNullArg(swipeRefreshLayout);
        swipeRefreshLayout.post(new Runnable()
        {
            @Override
            public void run()
            {
                swipeRefreshLayout.setRefreshing(isRefreshing);
            }
        });
    }
}

En su código, sustitúyalo mSwipeContainer.setRefreshing(isRefreshing)por Utils.setRefreshing(mSwipeContainer, isRefreshing): ahora solo se necesita cambiar un punto en el código una vez que se corrige el error, la Utilsclase. El método también se puede insertar en línea (y eliminar Utils).

Por lo general, no habrá una diferencia visual notable. Sin embargo, tenga en cuenta que la actualización pendiente podría mantener vivas sus Activityinstancias anteriores, manteniendo las SwipeRefreshLayoutjerarquías en su vista. Si eso es una preocupación, modifique el método para usar WeakReferences, pero normalmente no bloquea el hilo de la interfaz de usuario de todos modos, y por lo tanto demora gc solo por un par de milisegundos.

Gil Vegliach
fuente
2

También puede llamar a este método antes de setRefreshing.

    swipeRefreshLayout.measure(View.MEASURED_SIZE_MASK,View.MEASURED_HEIGHT_STATE_SHIFT);
swipeRefreshLayout.setRefreshing(true);

Me funcionó.

Leonardo Roese
fuente
1

Utilicé AppCompat Library com.android.support:appcompat-v7:21.0.3, usando tu mismo enfoque y funcionó. Entonces, actualizas la versión de esa biblioteca.

Consejo: RelativeLayoutno admite orientación, es un atributo para LinearLayout.

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, 
                                                 Bundle savedInstanceState) {       
  ViewGroup view = (ViewGroup) inflater.inflate(R.layout.license_fragment, 
           container, false);ButterKnife.inject(this, view);
    // Setting up Pull to Refresh
    swipeToRefreshLayout.setOnRefreshListener(this);
    // Indicator colors for refresh
    swipeToRefreshLayout.setColorSchemeResources(R.color.green, 
                R.color.light_green);
}

Diseño XML:

<android.support.v4.widget.SwipeRefreshLayout>

<ScrollView
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:paddingBottom="@dimen/activity_margin_vertical"
                    android:paddingTop="@dimen/activity_margin_vertical">

    <!-- Content -->
</ScrollView>

</android.support.v4.widget.SwipeRefreshLayout>
Jesús Castro
fuente
1

Además de Volodymyr Baydalka, use también el siguiente código:

swipeContainer.post(new Runnable() {
        @Override
        public void run() {
            swipeContainer.setRefreshing(false);
        }
    });

Explicación Implementé la solución dada por Volodymyr Baydalka (usando fragmentos) pero después de que se inició swipeReferesh nunca desapareció, incluso al llamar, swipeContainer.setRefreshing(false); así que tuve que implementar el código dado anteriormente que resolvió mi problema. cualquier otra idea de por qué sucede esto es bienvenida.

Saludos,

'com.android.support:appcompat-v7:22.2.1'

Junaid
fuente
1

@ niks.stack En respuesta a su respuesta, mostraría la rueda de progreso después de que onLayout()se haya hecho. Cuando lo usé directamente después onMeasure(), no honraría algunas de las compensaciones, pero usarlo después lo onLayout()hizo.

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);
    if (!mLaidOut) {
        mLaidOut = true;
        setRefreshing(mPreLayoutRefreshing);
    }
}

@Override
public void setRefreshing(boolean refreshing) {
    if (mLaidOut) {
        super.setRefreshing(refreshing);
    } else {
        mPreLayoutRefreshing = refreshing;
    }
}
mco
fuente
Estaba buscando una buena devolución de llamada después de onMeasure! Thnx. Me
molestaba
0

Mi solución (sin soporte v7) -

TypedValue typed_value = new TypedValue();
getTheme().resolveAttribute(android.R.attr.actionBarSize, typed_value, true);
swipeLayout.setProgressViewOffset(false, 0, getResources().getDimensionPixelSize(typed_value.resourceId));

if(!swipeLayout.isEnabled())
     swipeLayout.setEnabled(true);
swipeLayout.setRefreshing(true);
Artrmz
fuente
0

Estoy usando 'com.android.support:appcompat-v7:23.1.1'

swipeRefreshLayout.post(new Runnable() {
        @Override
        public void run() {
            swipeRefreshLayout.setRefreshing(true);
            getData();
        }
    });

anteriormente estaba usando el método swipeRefreshLayout.setRefreshing(true);inside getData(), por lo que no estaba funcionando. No sé por qué no funciona dentro del método.

Aunque swipeRefreshLayout.setRefreshing(true);solo lo usé una vez en mi Fragmento.

Chinmay
fuente
0

Prueba esto

mSwipeRefreshLayout.setNestedScrollingEnabled (verdadero);

Ashwin H
fuente
-1

Otra solución es crear un nuevo control y derivador de SwipeRefreshLayout. Anule la función OnMeasure y vuelva a alternar la actualización, si la actualización está activada. Utilizo esta solución en un proyecto xamarin y funciona bien. Aquí algunos ejemplos de código C #:

class MySwipeRefreshLayout : SwipeRefreshLayout
{
    /// <summary>
    /// used to indentify, if measure was called for the first time
    /// </summary>
    private bool m_MeasureCalled;

    public MvxSwipeRefreshLayout(Context context, IAttributeSet attrs)
        : base(context, attrs)
    {
    }

    public MvxSwipeRefreshLayout(Context context)
        : base(context)
    {
    }

    public override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        base.OnMeasure(widthMeasureSpec, heightMeasureSpec);

        if (!m_MeasureCalled)
        {
            //change refreshing only one time
            m_MeasureCalled = true;

            if (Refreshing)
            {
                Refreshing = false;
                Refreshing = true;
            }
        }
    }
}
Alchy
fuente