Invalidate() на самом деле не перерисовывает детей
Итак, у меня довольно странные настройки, и я получаю некоторые странные визуальные ошибки. По сути, у меня есть два вида в относительном макете: первый - это просто фоновое изображение ImageView; второе - это то же фоновое изображение, но размытое, что создает эффект замерзшего стекла:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/profile_holder"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<!-- background -->
<ImageView
android:id="@+id/backgroundImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginLeft="0dp"
android:layout_marginTop="0dp"
android:scaleType="centerCrop"
android:src="@drawable/default_logo" />
<!-- blurry background -->
<com.custom.MaskedBlurredBackgroundView_
android:id="@+id/blurred_background"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginLeft="0dp"
android:layout_marginTop="0dp" />
<ScrollView
android:id="@+id/scrolly"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true" >
<com.custom.HalvedLinearLayout_
android:paddingTop="100dp"
android:id="@+id/profileHolder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<Button
android:id="@+id/profileButtonTracks"
style="@style/ProfileButtons"
android:drawableLeft="@drawable/ic_profile_menu_music"
android:text="@string/profile_button_tracks" />
...
Размытый фон должен находиться только за представлением HalvedLinearLayout_, которое прокручивается вверх, поэтому мне нужно, чтобы размытый фон маскировался над HalvedLinearLayout_, чтобы не размытый фон просвечивал:
scrolly = ((ScrollView) rootView.findViewById(R.id.scrolly));
scrolly.setOnScrollListener(new ScrollView.OnScrollListener() {
private boolean hasScrolled = false;
@Override
public void onScrollChanged(int l, int t, int oldl, int oldt) {
if (!hasScrolled) {
hasScrolled = true;
Logger.action(Logger.Action.BioView, band);
}
positionMask();
}
});
...
protected void positionMask() {
if (blurredBackground == null) return;
Logger.d(TAG, "positionMask() " + rootView.getId());
//blurredBackground.setMaskBottom(blurredBackground.getHeight() - profileHolder.getPaddingTop() - scrollY);
blurredBackground.setMaskBottom(profileHolder.getTop() + profileHolder.getPaddingTop() - scrolly.getScrollY());
//blurredBackground.invalidate();
//profileHolder.invalidate();
rootView.postInvalidate();
}
Теперь проблема в том, что все работает так, как должно, за исключением огромного очевидного факта, что когда вы прокручиваете ScrollView, blurredBackground фактически рисует НАД HalvedLinearLayout_, стирая в нем кнопки. (но только на полпути, иногда. Иногда это будет хорошо, иногда половина кнопки будет перетянута, а другая половина сохранена... все виды странных сбоев.)
Я начал отладку и заметил кое-что интересное: вызов invalidate() для rootView фактически не делает инвалидами () все дочерние элементы. Я переопределил все недействительные и нарисованные функции некоторыми вещами:
public class MaskedBlurredBackgroundView extends BlurredBackgroundView {
/***
* Sets the mask to cover everything above the given Y coordinate
* @param t Y Coordinate in pixels
*/
public void setMaskBottom(int y) {
maskBottom = y;
}
private void clipCanvas(Canvas canvas) {
Logger.d(TAG, "dispatchDraw clipping: " + maskBottom + ", " + getWidth() + "," + getHeight());
canvas.clipRect(0, maskBottom, getWidth(), getHeight(), Region.Op.REPLACE);
}
@Override
protected void dispatchDraw(Canvas canvas) {
canvas.save();
clipCanvas(canvas);
super.dispatchDraw(canvas);
canvas.restore();
}
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
Logger.d(TAG, "drawChild: " + child + getWidth() + "," + getHeight());
return super.drawChild(canvas, child, drawingTime);
}
@Override
public void invalidate() {
Logger.d(TAG, "invalidate: " + getWidth() + "," + getHeight());
super.invalidate();
}
И теперь, когда я просматриваю журнал, rootView.invalidate() запускается при прокрутке, но дети никогда не перерисовывают:
07-23 12:36:49.328: D/BandDetailFrag(12982): positionMask() 2131165298
07-23 12:36:49.348: D/BandDetailFrag(12982): positionMask() 2131165298
07-23 12:36:49.348: D/BandDetailFrag(12982): positionMask() 2131165298
07-23 12:36:49.368: D/BandDetailFrag(12982): positionMask() 2131165298
07-23 12:36:49.368: D/BandDetailFrag(12982): positionMask() 2131165298
07-23 12:36:49.378: D/BandDetailFrag(12982): positionMask() 2131165298
07-23 12:36:49.398: D/BandDetailFrag(12982): positionMask() 2131165298
07-23 12:36:49.418: D/BandDetailFrag(12982): positionMask() 2131165298
07-23 12:36:49.448: D/BandDetailFrag(12982): positionMask() 2131165298
07-23 12:36:49.478: D/BandDetailFrag(12982): positionMask() 2131165298
Как заставить детей перерисовать, ПО ПРАВИЛЬНОМУ ПОРЯДКУ? Прямо сейчас я предполагаю, что они рисуют не по порядку, и поэтому размытый фон рисует поверх всего остального.
Вот скриншот глюка:
2 ответа
Я столкнулся с этой же проблемой с invalidate()
и поскольку в SO нет хорошего ответа, я решил опубликовать свое решение.
Это рекурсивная функция, которая будет перебирать все дочерние представления ViewGroup
, Если ребенок ViewGroup
, он вернется и обыщет ребенка. В противном случае, это вызовет invalidate()
на ребенка (это случай прекращения).
public void invalidateRecursive(ViewGroup layout) {
int count = layout.getChildCount();
View child;
for (int i = 0; i < count; i++) {
child = layout.getChildAt(i);
if(child instanceof ViewGroup)
invalidateRecursive((ViewGroup) child);
else
child.invalidate();
}
}
Параметр layout
должно быть в состоянии быть что-нибудь типа ViewGroup
(LinearLayout
, RelativeLayout
и т. д.) делая это довольно общее решение.
Он работает для моих целей, но я не подверг его тщательному тестированию. Если есть какие-либо ошибки, пожалуйста, прокомментируйте и дайте мне знать.
Хорошо, я понял, как избавиться от сбоев, по крайней мере: все, что мне нужно было сделать, это заменить "Region.Op.REPLACE" на "Region.Op.INTERSECT", и теперь это работает!
Invalidate () по-прежнему не перерисовывает детей, но я думаю, что я позволю этому слайду теперь, когда он работает.