SwipeRefreshLayout setRefreshing () não mostrando o indicador inicialmente

144

Eu tenho um layout muito simples, mas quando clico setRefreshing(true)no onActivityCreated()meu fragmento, ele não aparece inicialmente.

Ele só mostra quando eu faço um puxão para atualizar. Alguma idéia de por que não está aparecendo 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 do 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
    }
}
thunderousNinja
fonte
Qual versao voce usa?
Ahmed Hegazy 11/11
compilar "com.android.support:appcompat-v7:21.0.0"
thunderousNinja
Confirmo que esse problema aconteceu comigo a partir dessa versão. As versões anteriores não têm problemas com isso. Vou postar a solução, se eu conseguir alguma.
Ahmed Hegazy 11/11
Deixe-me tentar uma versão anterior
thunderousNinja
Também não está trabalhando na v20 para mim. Em qual versão está trabalhando para você?
usar o seguinte

Respostas:

307

Diante do mesmo problema. Minha solução -

mSwipeRefreshLayout.post(new Runnable() {
    @Override
    public void run() {
        mSwipeRefreshLayout.setRefreshing(true);
    }
});
Volodymyr Baydalka
fonte
1
Funciona bem, mas não sei por que precisamos fazer isso em vez de mSwipeRefreshLayout.setRefreshing (true);
Cocorico 27/05
1
Isso não é uma boa solução / solução alternativa. Se o usuário estiver no modo avião, seu refreshLayout iniciará a atualização em seu próprio encadeamento depois de já iniciar a solicitação de rede e receber sua resposta (falha neste caso). Ao manipulá-lo para interromper o refreshLayout, ele não funcionaria, pois ainda não foi iniciado! Em outras palavras, o refreshLayout iniciaria a atualização depois que você receber sua resposta. Boa sorte interrompê-lo
Samer
1
Pelo que me lembro, as "postagens" são sincronizadas e executadas na ordem de adição. Então você pode adicionar outra postagem para pará-la.
Volodymyr Baydalka
2
Talvez pareça mais simples na implementação, mas não é agradável. @ solução niks.stack abaixo é melhor que não requer qualquer alteração no código de usá-lo, por isso, quando finalmente este bug foi corrigido no lib apoio, você acabou de voltar para apoio lib SwipeRefreshLayout
Marcin Orlowski
100

Veja a resposta de Volodymyr Baydalka.

Essas são as soluções alternativas antigas.

Isso costumava funcionar na versão anterior do android.support.v4, mas a partir da versão 21.0.0 em andamento, ele não funciona e ainda existe com o android.support.v4:21.0.3lançamento de 10 a 12 de dezembro de 2014, e esse é o motivo.

O indicador SwipeRefreshLayout não aparece quando setRefreshing(true)é chamado antes doSwipeRefreshLayout.onMeasure()

Gambiarra:

chamando setProgressViewOffset()o SwipeRefreshLayoutque invalida a exibição do círculo do layout causando SwipeRefreshLayout.onMeasure()a chamada imediatamente.

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

Melhor solução alternativa

Como a barra de ação pode ficar mais fina quando a orientação muda ou você definiu o tamanho da barra de ação manualmente. Definimos o deslocamento em pixels da parte superior desta visualização, na qual o controle giratório de progresso deve ser redefinido após um gesto de furto bem-sucedido para o tamanho atual da barra de ação.

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));

ATUALIZAÇÃO 20 de novembro de 2014

Se não for muito crucial para o seu aplicativo exibir o SwipeRefreshLayout após o início da exibição. Você pode publicá-lo em um momento no futuro usando manipuladores ou qualquer coisa que desejar.

como um exemplo.

handler.postDelayed(new Runnable() {

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

ou como a resposta de Volodymyr Baydalka mencionou.

Aqui está o problema no rastreador de problemas do Android. Faça um voto positivo para mostrar a eles que precisamos que seja corrigido.

Ahmed Hegazy
fonte
Você fez muitos testes com o delay time? Eu estou usando 500no meu Galaxy S4. Não tenho certeza se isso seria um problema em outro dispositivo.
theblang
Eu não fiz muitos testes com o tempo de atraso, mas acho que 500faria fine.I testei no emulator.Eu só queria estar safecom os 1000milissegundos
Ahmed Hegazy
3
Não há necessidade de postar atrasado. você pode apenas postar. postar sem demora significa apenas "faça isso assim que terminar o que está fazendo agora". e o que está fazendo agora é medir e definir sua interface do usuário.
Oren
47

Minha solução é substituir 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
fonte
3
A vantagem da sua solução é manter o deslocamento da vista intocado! Dessa forma, continuamos em conformidade com o padrão de design especificado aqui: google.com/design/spec/patterns/… . Obrigado!
Igor de Lorenzi
Você pode explicar como isso funciona? Está funcionando, mas não entendo direito o funcionamento interno. Só estou perdendo algo trivial?
Sree
setRefreshing obras somente após chamada onMeasure, então nós armazenar bandeira refrescante local, e em primeira convocação onMeasure aplicá-lo
nikita.zhelonkin
5
Adoro esta solução porque significa que o código que chama SwipeRefreshLayout pode ser exatamente como desejamos, sem complicações. Ele basicamente corrigiu o bug no SwipeRefreshLayout. SwipeRefreshLayout realmente deve ser implementado dessa maneira.
DataGraham
Essa resposta pode não parecer tão direta, mas corrige o problema de maneira agradável em muitas versões da biblioteca de suporte (no meu caso, é 23.1.1). De acordo com o ticket, esse problema não foi corrigido em 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
fonte
3
Esta resposta é o único que não é um hack para que ele deve ser aceito
Heinrich
1
Apenas uma pequena coisa: .removeGlobalOnLayoutListener deve ser .removeOnGlobalLayoutListener
mkuech
1
@mkeuch depende de qual API sua segmentação. Se sua segmentação sob API16, você deve verificar a versão da API e usar as duas.
Marko
Gosto mais desta resposta do que a resposta mais votada, porque fica mais claro o motivo pelo qual é usada.
Marcel Bro
Devo me corrigir, esta solução nem sempre funciona para mim. Em alguns casos, as chamadas setRefreshing(false)encerradas onGlobalLayoutListener()não descartam o indicador de carregamento.
Marcel Bro
4

Com base na resposta de Volodymyr Baydalka , essa é apenas uma pequena idéia que o ajudará a manter o código limpo. Ele merece um post, acredito: você poderá reverter o comportamento facilmente da postagem para a chamada direta ao método, assim que o bug for corrigido.

Escreva uma classe de utilitário 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);
            }
        });
    }
}

No seu código, substitua mSwipeContainer.setRefreshing(isRefreshing)por Utils.setRefreshing(mSwipeContainer, isRefreshing): agora apenas um ponto no código precisa ser alterado após a correção do bug, a Utilsclasse. O método também pode ser incorporado em seguida (e removido Utils).

Geralmente não haverá diferença visual perceptível. Lembre-se de que a atualização pendente pode manter vivas suas Activityinstâncias antigas , mantendo as SwipeRefreshLayouthierarquias de exibição. Se isso for uma preocupação, ajuste o método para usar WeakReferences, mas normalmente você não bloqueia o encadeamento da interface do usuário e, portanto, atrasa o gc em apenas alguns milissegundos.

Gil Vegliach
fonte
2

Além disso, você pode chamar esse método antes de setRefreshing ..

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

Funcionou para mim.

Leonardo Roese
fonte
1

Eu usei a AppCompat Library com.android.support:appcompat-v7:21.0.3, usando sua mesma abordagem e funcionou. Então, você atualiza a versão dessa biblioteca.

Conselho: RelativeLayoutnão suporta orientação, é um 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);
}

XML de layout:

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

Além de Volodymyr Baydalka, use também o seguinte código:

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

Explicação Eu implementei a solução fornecida por Volodymyr Baydalka (usando fragmentos), mas após o furtoReferesh iniciado, ele nunca desapareceu nem mesmo ao chamarswipeContainer.setRefreshing(false); então tivemos que implementar o código fornecido acima, que resolveu meu problema. mais idéias sobre por que isso acontece são bem-vindas.

Saudações,

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

Junaid
fonte
1

@ niks.stack Em resposta à sua resposta, eu mostraria a roda do progresso depois de onLayout()ter sido feito. Quando eu o usei diretamente depois onMeasure(), ele não honraria algumas das compensações, mas usá-lo depois onLayout()fez.

@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
fonte
Eu estava procurando um bom retorno de chamada após onMeasure! Thnx. Era irritante a merda fora de mim que na corrida inicial o deslocamento estava fora ...
xdbas
0

Minha solução (sem suporte 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
fonte
0

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

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

anteriormente eu estava usando o método swipeRefreshLayout.setRefreshing(true);interno getData(), então não estava funcionando. Não sei por que não está funcionando dentro do método.

Embora eu usei swipeRefreshLayout.setRefreshing(true);apenas uma vez no meu fragmento.

Chinmay
fonte
0

Tente isto

mSwipeRefreshLayout.setNestedScrollingEnabled (true);

Ashwin H
fonte
-1

Outra solução alternativa é criar um novo controle e derivador do SwipeRefreshLayout. Substitua a função OnMeasure e alterne a atualização novamente, se a atualização estiver ativada. Eu uso essa solução em um projeto xamarin e funciona bem. Aqui estão alguns exemplos 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
fonte