SwipeRefreshLayout setRefreshing () спочатку не показує індикатор


144

У мене є дуже простий макет , але коли я дзвоню setRefreshing(true)в onActivityCreated()моєму фрагменті, він не показує на початковому етапі.

Це відображається лише тоді, коли я підтягуюсь, щоб оновити. Будь-які ідеї, чому вона не відображається спочатку?

Фрагмент 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>

Код фрагмента:

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
    }
}

Яку версію ви використовуєте?
Ахмед Гегазі

компілювати "com.android.support:appcompat-v7:21.0.0"
грозовийNinja

Я підтверджую, що ця проблема трапилася зі мною з цієї версії, коли ви працюєте. Більш ранні версії з цим не мають жодних проблем. Я опублікую рішення, якщо отримаю таке.
Ахмед Гегазі

Дозвольте спробувати більш ранню версію
громовитий

Також для мене не працює v20. Яка версія працює для вас?
громовитий

Відповіді:


307

Зіткнувся з тим же випуском. Моє рішення -

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

1
Добре працює, але я не знаю, навіщо нам це робити замість mSwipeRefreshLayout.setRefreshing (вірно);
Кокоріко


1
Це не є гарним рішенням / вирішенням проблеми. Якщо користувач перебуває в режимі літака, ваша refreshLayout почне оновлення на власному потоці після того, як вже запустить мережевий запит і отримає його відповідь (помилка в цьому випадку). Якщо обробити його для зупинки refreshLayout, воно не працюватиме, оскільки воно ще не почалося! Іншими словами, refreshLayout почне оновлюватись після отримання вашої відповіді. Успіху зупинивши це
Самер

1
Як я пам’ятаю, «повідомлення» синхронізуються та працюють у порядку додавання. Тому ви можете додати ще одну публікацію, щоб зупинити її.
Володимир Байдалка

2
Це, можливо, виглядає найпростіше в реалізації, але це не приємно. @ niks.stack рішення нижче - краще, оскільки не потребує змін у коді, використовуючи його, тому коли врешті-решт ця помилка буде виправлена ​​у підтримці lib, ви просто переключитесь на підтримку lib SwipeRefreshLayout
Marcin Orlowski

100

Дивіться натомість відповідь Володимира Байдалки.

Це старі обхідні шляхи.

Раніше він працював над більш ранньою версією android.support.v4, але з версії 21.0.0, яка триває, вона не працює і все ще існує з android.support.v4:21.0.3випущеним 10-12 грудня 2014 року, і це причина.

Індикатор SwipeRefreshLayout не з'являється, коли setRefreshing(true)виклик передSwipeRefreshLayout.onMeasure()

Обхід:

закликаючи setProgressViewOffset()до того, SwipeRefreshLayoutщо вимикає подання кола макета, викликаючи SwipeRefreshLayout.onMeasure()негайний виклик.

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

ОНОВЛЕННЯ Краще вирішення

Оскільки панель дій може бути тоншою при зміні орієнтації або ви встановили розмір панелі дій вручну. Ми встановлюємо зсув у пікселях у верхній частині цього виду, при якому спінер прогресу повинен скинутись після успішного проведення пальцем до поточного розміру панелі дій.

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

ОНОВЛЕННЯ 20 листопада 2014 року

Якщо це не дуже важливо для вашої програми, показуйте SwipeRefreshLayout після запуску перегляду. Ви можете просто опублікувати його на час у майбутньому, використовуючи обробники або будь-яку річ, яку ви хочете.

як приклад.

handler.postDelayed(new Runnable() {

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

або як згадується у відповіді Володимира Байдалки.

Ось проблема в трекері проблем Android. Будь ласка, підкажіть це, щоб показати їм, що нам потрібно це виправити.


Ви багато тестували з delay time? Я використовую 500на своєму Galaxy S4. Не впевнений, чи це буде проблемою на іншому пристрої.
theblang

Я не робив багато тестування з часом затримки, але, думаю 500, зробив би це добре. Я перевірив це на. emulatorЯ просто хотів бути safeз 1000мілісекундами
Ахмед Гегазі

3
Не потрібно відкладати повідомлення. ви можете просто дописувати. публікація без затримки просто означає "зробіть цю справу, коли ви закінчите з тим, що зараз робите". і що він зараз робить - це вимірювання та розміщення вашого інтерфейсу користувача.
Орен

47

Моє рішення - перекрити 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;
        }
    }
}

3
Перевага вашого рішення полягає в тому, щоб зберегти зсув перегляду недоторканим! Таким чином , ми по- , як і раніше відповідно до шаблону проектування , зазначеними тут: google.com/design/spec/patterns / ... . Дякую!
Ігор де Лоренці

Чи можете ви поясніть, як це працює? Це працює, але я не дуже розумію внутрішню роботу. Я просто пропускаю щось тривіальне?
Sree

setRefreshing працює лише після дзвінка onMeasure, тому ми зберігаємо локальний освіжаючий прапор, а при першому дзвінку onMeasure застосуємо його
nikita.zhelonkin

5
Мені подобається це рішення, оскільки це означає, що код, що викликає SwipeRefreshLayout, може бути таким, яким ми хочемо, без будь-яких ускладнень. В основному виправлено помилку в SwipeRefreshLayout. SwipeRefreshLayout насправді просто слід реалізувати таким чином.
DataGraham

Ця відповідь може виглядати не так прямо, але вона добре вирішує проблему через багато версій бібліотеки підтримки (у моєму випадку це 23.1.1). Згідно з квитком, це питання не вирішено @ 23.2. code.google.com/p/android/isissue/detail?id=77712
Роберт

20
mRefreshLayout.getViewTreeObserver()
                .addOnGlobalLayoutListener(
                        new ViewTreeObserver.OnGlobalLayoutListener() {
                            @Override
                            public void onGlobalLayout() {
                                mRefreshLayout
                                        .getViewTreeObserver()
                                        .removeGlobalOnLayoutListener(this);
                                mRefreshLayout.setRefreshing(true);
                            }
                        });

3
Ця відповідь є єдиною, яка не є злом, тому її слід прийняти
Генріх

1
Просто невелика річ: .removeGlobalOnLayoutListener має бути .removeOnGlobalLayoutListener
mkuech

1
@mkeuch залежить від того, на який API ви орієнтуєтесь. Якщо ви орієнтуєтесь за API16, вам потрібно перевірити версію API та використовувати обидва.
Марко

Мені це подобається трохи більше, ніж відповідь, що найбільш схвалюється, тому що зрозуміліше, чому він використовується.
Marcel Bro

Я повинен виправити себе, це рішення НЕ завжди працює для мене. У деяких випадках виклик setRefreshing(false)завершеного onGlobalLayoutListener()не відхиляє показник завантаження.
Marcel Bro


4

Виходячи з відповіді Володимира Байдалки , це лише невелика ідея, яка допоможе вам зберегти чистий код. Я заслуговую на публікацію, на яку я вірю: ви зможете легко повернути свою поведінку від публікації до прямого виклику методу, як тільки виправлено помилку.

Напишіть клас корисності, наприклад:

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

У вашому коді замініть mSwipeContainer.setRefreshing(isRefreshing)на Utils.setRefreshing(mSwipeContainer, isRefreshing): тепер лише один пункт у коді потрібно змінити, як тільки виправлено помилку, Utilsклас. Метод також може бути вкладений (і видалений з Utils).

Зазвичай не помітна візуальна різниця. Майте на увазі, що те, що оновлення, що очікує, може зберегти живі ваші старі Activityекземпляри, дотримуючись SwipeRefreshLayoutїх ієрархії. Якщо це викликає занепокоєння, налаштуйте метод на використання WeakReferences, але зазвичай ви все одно не блокуєте потік інтерфейсу користувача і, таким чином, затримуєте gc лише на пару мілісекунд.


2

Також ви можете зателефонувати до цього методу перед setRefreshing ..

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

Для мене це працює.


1

Я використовував бібліотеку AppCompat com.android.support:appcompat-v7:21.0.3, використовуючи ваш той же підхід, і це спрацювало. Отже, ви оновлюєте версію цієї бібліотеки.

Порада: RelativeLayoutне підтримує орієнтацію, це атрибут 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:

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

1

Окрім Володимира Байдалки, використовуйте також такий код:

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

Пояснення Я реалізував рішення, яке надав Володимир Байдалка (використовуючи фрагменти), але після запуску swipeReferesh він ніколи не згасав навіть при виклику, swipeContainer.setRefreshing(false); тому довелося реалізувати наведений вище код, який вирішив мою проблему. будь-які інші ідеї, чому це відбувається, дуже вітаються.

З повагою,

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


1

@ niks.stack У відповідь на його відповідь я б показав колесо прогресу після того, onLayout()як було зроблено. Коли я використовував його безпосередньо після цього onMeasure(), він не вшанував би деякі компенсації, але використовуючи його після onLayout()цього.

@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;
    }
}

Я шукав хорошого зворотного дзвінка після onMeasure! Thnx. Мене дратувало лайно, що на початковому етапі зміщення було вимкнено ...
xdbas

0

Моє рішення (без підтримки 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);

0

Я використовую 'com.android.support:appcompat-v7:23.1.1'

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

раніше я використовував swipeRefreshLayout.setRefreshing(true);внутрішній getData()метод, тому він не працював. Я не знаю, чому це не працює всередині методу.

Хоча я використовував swipeRefreshLayout.setRefreshing(true);лише один раз у своєму Фрагменті.



-1

Іншим вирішенням є створення нового елемента управління та виведення з SwipeRefreshLayout. Перемініть функцію OnMeasure і перемкніть оновлення знову, якщо активовано оновлення. Я використовую це рішення в проекті xamarin, і воно добре працює. Ось приклад 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;
            }
        }
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.