Ответ 1
Обновление
Я немного изменил код, чтобы решить оставшиеся проблемы - по крайней мере, те, которые я могу воспроизвести. Ключевое обновление состояло в том, чтобы избавиться от dy
только тогда, когда AppBar был расширен или скомпенсирован. На первой итерации dispatchNestedPreScroll()
удалял прокрутку без проверки состояния AppBar для сложенного состояния.
Другие изменения незначительны и подпадают под категорию очистки. Кодовые блоки обновляются ниже.
В этом ответе рассматривается вопрос о RecyclerView
. Другой ответ, который я дал, остается и стоит здесь. RecyclerView
имеет те же проблемы, что и NestedScrollView
, которые были введены в 26.0.0-бета2 библиотек поддержки.
Нижеприведенный код основывается на этом ответе на соответствующий вопрос, но включает в себя исправление ошибочного поведения AppBar. Я удалил код, который фиксировал нечетную прокрутку, потому что он больше не нужен.
AppBarTracking.java
public interface AppBarTracking {
boolean isAppBarIdle();
boolean isAppBarExpanded();
}
MyRecyclerView.java
public class MyRecyclerView extends RecyclerView {
public MyRecyclerView(Context context) {
this(context, null);
}
public MyRecyclerView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
private AppBarTracking mAppBarTracking;
private View mView;
private int mTopPos;
private LinearLayoutManager mLayoutManager;
@Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow,
int type) {
// App bar latching trouble is only with this type of movement when app bar is expanded
// or collapsed. In touch mode, everything is OK regardless of the open/closed status
// of the app bar.
if (type == ViewCompat.TYPE_NON_TOUCH && mAppBarTracking.isAppBarIdle()
&& isNestedScrollingEnabled()) {
// Make sure the AppBar stays expanded when it should.
if (dy > 0) { // swiped up
if (mAppBarTracking.isAppBarExpanded()) {
// Appbar can only leave its expanded state under the power of touch...
consumed[1] = dy;
return true;
}
} else { // swiped down (or no change)
// Make sure the AppBar stays collapsed when it should.
// Only dy < 0 will open the AppBar. Stop it from opening by consuming dy if needed.
mTopPos = mLayoutManager.findFirstVisibleItemPosition();
if (mTopPos == 0) {
mView = mLayoutManager.findViewByPosition(mTopPos);
if (-mView.getTop() + dy <= 0) {
// Scroll until scroll position = 0 and AppBar is still collapsed.
consumed[1] = dy - mView.getTop();
return true;
}
}
}
}
boolean returnValue = super.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type);
// Fix the scrolling problems when scrolling is disabled. This issue existed prior
// to 26.0.0-beta2.
if (offsetInWindow != null && !isNestedScrollingEnabled() && offsetInWindow[1] != 0) {
offsetInWindow[1] = 0;
}
return returnValue;
}
@Override
public void setLayoutManager(RecyclerView.LayoutManager layout) {
super.setLayoutManager(layout);
mLayoutManager = (LinearLayoutManager) getLayoutManager();
}
public void setAppBarTracking(AppBarTracking appBarTracking) {
mAppBarTracking = appBarTracking;
}
@SuppressWarnings("unused")
private static final String TAG = "MyRecyclerView";
}
ScrollingActivity.java
public class ScrollingActivity extends AppCompatActivity
implements AppBarTracking {
private MyRecyclerView mNestedView;
private int mAppBarOffset;
private boolean mAppBarIdle = false;
private int mAppBarMaxOffset;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scrolling);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
mNestedView = findViewById(R.id.nestedView);
final AppBarLayout appBar = findViewById(R.id.app_bar);
appBar.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
@Override
public final void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
mAppBarOffset = verticalOffset;
// mAppBarOffset = 0 if app bar is expanded; If app bar is collapsed then
// mAppBarOffset = mAppBarMaxOffset
// mAppBarMaxOffset is always <=0 (-AppBarLayout.getTotalScrollRange())
// mAppBarOffset should never be > zero or less than mAppBarMaxOffset
mAppBarIdle = (mAppBarOffset >= 0) || (mAppBarOffset <= mAppBarMaxOffset);
}
});
appBar.post(new Runnable() {
@Override
public void run() {
mAppBarMaxOffset = -appBar.getTotalScrollRange();
}
});
findViewById(R.id.disableNestedScrollingButton).setOnClickListener(new OnClickListener() {
@Override
public void onClick(final View v) {
// If the AppBar is fully expanded or fully collapsed (idle), then disable
// expansion and apply the patch; otherwise, set a flag to disable the expansion
// and apply the patch when the AppBar is idle.
setExpandEnabled(false);
}
});
findViewById(R.id.enableNestedScrollingButton).setOnClickListener(new OnClickListener() {
@Override
public void onClick(final View v) {
setExpandEnabled(true);
}
});
mNestedView.setAppBarTracking(this);
mNestedView.setLayoutManager(new LinearLayoutManager(this));
mNestedView.setAdapter(new Adapter() {
@Override
public ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(
android.R.layout.simple_list_item_1,
parent,
false)) {
};
}
@SuppressLint("SetTextI18n")
@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
((TextView) holder.itemView.findViewById(android.R.id.text1)).setText("item " + position);
}
@Override
public int getItemCount() {
return 100;
}
});
}
private void setExpandEnabled(boolean enabled) {
mNestedView.setNestedScrollingEnabled(enabled);
}
@Override
public boolean isAppBarExpanded() {
return mAppBarOffset == 0;
}
@Override
public boolean isAppBarIdle() {
return mAppBarIdle;
}
@SuppressWarnings("unused")
private static final String TAG = "ScrollingActivity";
}
Что здесь происходит?
Из этого вопроса было очевидно, что макет не смог привязать панель приложения, закрытую или открытую, как это было необходимо, когда палец пользователя не был на экране. При перетаскивании панель приложений ведет себя так, как должна.
В версии 26.0.0-beta2 были введены некоторые новые методы - в частности dispatchNestedPreScroll() с новым аргументом type
. Аргумент type
указывает, если движение, указанное dx
и dy
, связано с тем, что пользователь коснулся экрана ViewCompat.TYPE_TOUCH
или не ViewCompat.TYPE_NON_TOUCH
.
Несмотря на то, что конкретный код, который вызывает проблему, не был идентифицирован, привязка исправления заключается в том, чтобы убить вертикальное движение в dispatchNestedPreScroll()
(распоряжаться dy
), когда это необходимо, не допуская распространения вертикального движения. Фактически, панель приложения должна быть вставлена на место при расширении и не разрешается начинать закрываться, пока она не закрывается жестом касания. Панель приложения также будет закрыта при закрытии до тех пор, пока RecyclerView
не будет располагаться в самой верхней части и будет достаточно dy
, чтобы открыть панель приложений при выполнении жестов касания.
Итак, это не столько исправление, сколько обескуражение проблемных условий.
Последняя часть кода MyRecyclerView
связана с проблемой, которая была идентифицирована в этом question, касающемся неправильных перемещений прокрутки, когда вложенная прокрутка отключена. Это та часть, которая приходит после вызова супер dispatchNestedPreScroll()
, который изменяет значение offsetInWindow[1]
. Мысль за этим кодом такая же, как и в принятом ответе на вопрос. Единственное отличие состоит в том, что, поскольку основной вложенный код прокрутки изменился, аргумент offsetInWindow
имеет значение null. К счастью, это кажется ненулевым, когда это имеет значение, поэтому последняя часть продолжает работать.
Предостережение заключается в том, что это "исправление" очень специфично для заданного вопроса и не является общим решением. Вероятно, исправление будет иметь очень короткий срок хранения, так как я ожидаю, что такая очевидная проблема будет решена в ближайшее время.