Can someone with google dev team explain how to avoid this crash on pre-ics devices? In my case an ImageButton on the ListView item is the anchor of the PopupWindow to create a dropdown. I have tried everything popup.dismiss() , popup= null, etc but nothing seems to prevent this being an issue when the adapter is reset.
I am getting the following exception:
FATAL EXCEPTION: main
java.lang.NullPointerException
at android.widget.PopupWindow$1.onScrollChanged(PopupWindow.java:132)
05-21 17:02:27.736: E/AndroidRuntime(25836): at
android.view.ViewTreeObserver.dispatchOnScrollChanged(ViewTreeObserver.java:607)
This Popup is on a list item. Once the last list item is removed from the ListView I resetAdapter to set a footer. Then when I move away from the screen this error occurs.
Not in Ice Cream Sandwich: Icecream Sandwich. See https://android.googlesource.com/platform/frameworks/base/+/749b0eb2c9a52bb188fd8900859b3725889e0ec0%5E!/
This suggests a fix related to null anchor of PopupWindow. What can be done?
In my case a button in ListView item is the anchor of the popup window.
and same issue here:
https://github.com/JakeWharton/ActionBarSherlock/issues/487
Not sure if OP still needs this since it is half year later that I just saw this..
This is fixed in ICS. However, you may assign a fixed scroll listener via reflection!
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.ICE_CREAM_SANDWICH){
try {
final Field fAnchor = PopupWindow.class.getDeclaredField("mAnchor");
fAnchor.setAccessible(true);
Field listener = PopupWindow.class.getDeclaredField("mOnScrollChangedListener");
listener.setAccessible(true);
final ViewTreeObserver.OnScrollChangedListener originalListener = (ViewTreeObserver.OnScrollChangedListener) listener.get(window);
ViewTreeObserver.OnScrollChangedListener newListener=
new ViewTreeObserver.OnScrollChangedListener() {
public void onScrollChanged() {
try {
// PopupWindow implementation has WeakReference<View>
WeakReference<View> mAnchor = (WeakReference<View>) fAnchor.get(window);
if (mAnchor == null || mAnchor.get() == null) {
return;
} else {
originalListener.onScrollChanged();
}
} catch (Exception e) {
e.printStackTrace();
}
}
};
listener.set(window,newListener);
} catch (Exception e) {
e.printStackTrace();
}
}
Related
At my application I am using container where I change several fragments. At some fragments I have to hide actionbar at parent activity. I managed to do it, but the process of hiding and viewing toolbar is supported with weird and uncomfortable animation. I hide and show toolbar via:
Objects.requireNonNull(getSupportActionBar()).hide();
Maybe I can clear animation?
I managed to find a solution for my problem with this method:
public static void disableShowHideAnimation(ActionBar actionBar) {
try
{
actionBar.getClass().getDeclaredMethod("setShowHideAnimationEnabled", boolean.class).invoke(actionBar, false);
}
catch (Exception exception)
{
try {
Field mActionBarField = actionBar.getClass().getSuperclass().getDeclaredField("mActionBar");
mActionBarField.setAccessible(true);
Object icsActionBar = mActionBarField.get(actionBar);
Field mShowHideAnimationEnabledField = icsActionBar.getClass().getDeclaredField("mShowHideAnimationEnabled");
mShowHideAnimationEnabledField.setAccessible(true);
mShowHideAnimationEnabledField.set(icsActionBar,false);
Field mCurrentShowAnimField = icsActionBar.getClass().getDeclaredField("mCurrentShowAnim");
mCurrentShowAnimField.setAccessible(true);
mCurrentShowAnimField.set(icsActionBar,null);
}catch (Exception e){
//....
}
}
}
link.
When my listview is scrolling and then i touch somewhere else on my screen, the listview scrolling should stop. So is there a way to stop the vertical scroll of listView depending upon some condition?
I can disable the scroll before compiling and onTouch() i can return true to avoid scroll but i am not getting to stop it without touching it.
Can be done using reflection. Found this code somewhere.
try {
Field field = android.widget.AbsListView.class.getDeclaredField("mFlingRunnable");
field.setAccessible(true);
Object flingRunnable = field.get(listView);
if (flingRunnable != null) {
Method method = Class.forName("android.widget.AbsListView$FlingRunnable").getDeclaredMethod("endFling");
method.setAccessible(true);
method.invoke(flingRunnable);
}
} catch (Exception e) {}
I'm building an interface similar to the Google Hangouts chat interface. New messages are added to the bottom of the list. Scrolling up to the top of the list will trigger a load of previous message history. When the history comes in from the network, those messages are added to the top of the list and should not trigger any kind of scroll from the position the user had stopped when the load was triggered. In other words, a "loading indicator" is shown at the top of the list:
Which is then replaced in-situ with any loaded history.
I have all of this working... except one thing that I've had to resort to reflection to accomplish. There are plenty of questions and answers involving merely saving and restoring a scroll position when adding items to the adapter attached to a ListView. My problem is that when I do something like the following (simplified but should be self-explanatory):
public void addNewItems(List<Item> items) {
final int positionToSave = listView.getFirstVisiblePosition();
adapter.addAll(items);
listView.post(new Runnable() {
#Override
public void run() {
listView.setSelection(positionToSave);
}
});
}
Then what the user will see is a quick flash to the top of the ListView, then a quick flash back to the right location. The problem is fairly obvious and discovered by many people: setSelection() is unhappy until after notifyDataSetChanged() and a redraw of ListView. So we have to post() to the view to give it a chance to draw. But that looks terrible.
I've "fixed" it by using reflection. I hate it. At its core, what I want to accomplish is reset the first position of the ListView without going through the rigamarole of the draw cycle until after I've set the position. To do that, there's a helpful field of ListView: mFirstPosition. By gawd, that's exactly what I need to adjust! Unfortunately, it's package-private. Also unfortunately, there doesn't appear to be any way to set it programmatically or influence it in any way that doesn't involve an invalidate cycle... yielding the ugly behavior.
So, reflection with a fallback on failure:
try {
Field field = AdapterView.class.getDeclaredField("mFirstPosition");
field.setAccessible(true);
field.setInt(listView, positionToSave);
}
catch (Exception e) { // CATCH ALL THE EXCEPTIONS </meme>
e.printStackTrace();
listView.post(new Runnable() {
#Override
public void run() {
listView.setSelection(positionToSave);
}
});
}
}
Does it work? Yes. Is it hideous? Yes. Will it work in the future? Who knows? Is there a better way? That's my question.
How do I accomplish this without reflection?
An answer might be "write your own ListView that can handle this." I'll merely ask whether you've seen the code for ListView.
EDIT: Working solution with no reflection based on Luksprog's comment/answer.
Luksprog recommended an OnPreDrawListener(). Fascinating! I've messed with ViewTreeObservers before, but never one of these. After some messing around, the following type of thing appears to work quite perfectly.
public void addNewItems(List<Item> items) {
final int positionToSave = listView.getFirstVisiblePosition();
adapter.addAll(items);
listView.post(new Runnable() {
#Override
public void run() {
listView.setSelection(positionToSave);
}
});
listView.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
#Override
public boolean onPreDraw() {
if(listView.getFirstVisiblePosition() == positionToSave) {
listView.getViewTreeObserver().removeOnPreDrawListener(this);
return true;
}
else {
return false;
}
}
});
}
Very cool.
As I said in my comment, a OnPreDrawlistener could be another option to solve the problem. The idea of using the listener is to skip showing the ListView between the two states(after adding the data and after setting the selection to the right position). In the OnPreDrawListener(set with listViewReference.getViewTreeObserver().addOnPreDrawListener(listener);) you'll check the current visible position of the ListView and test it against the position which the ListView should show. If those don't match then make the listener's method return false to skip the frame and set the selection on the ListView to the right position. Setting the proper selection will trigger the draw listener again, this time the positions will match, in which case you'd unregister the OnPreDrawlistener and return true.
I was breaking up my head until I found a solution similar to this.
Before adding a set of items you have to save top distance of the firstVisible item and after adding the items do setSelectionFromTop().
Here is the code:
// save index and top position
int index = mList.getFirstVisiblePosition();
View v = mList.getChildAt(0);
int top = (v == null) ? 0 : v.getTop();
// for (Item item : items){
mListAdapter.add(item);
}
// restore index and top position
mList.setSelectionFromTop(index, top);
It works without any jump for me with a list of about 500 items :)
I took this code from this SO post: Retaining position in ListView after calling notifyDataSetChanged
The code suggested by the question author works, but it's dangerous.
For instance, this condition:
listView.getFirstVisiblePosition() == positionToSave
may always be true if no items were changed.
I had some problems with this aproach in a situation where any number of elements were added both above and below the current element. So I came up with a sligtly improved version:
/* This listener will block any listView redraws utils unlock() is called */
private class ListViewPredrawListener implements OnPreDrawListener {
private View view;
private boolean locked;
private ListViewPredrawListener(View view) {
this.view = view;
}
public void lock() {
if (!locked) {
locked = true;
view.getViewTreeObserver().addOnPreDrawListener(this);
}
}
public void unlock() {
if (locked) {
locked = false;
view.getViewTreeObserver().removeOnPreDrawListener(this);
}
}
#Override
public boolean onPreDraw() {
return false;
}
}
/* Method inside our BaseAdapter */
private updateList(List<Item> newItems) {
int pos = listView.getFirstVisiblePosition();
View cell = listView.getChildAt(pos);
String savedId = adapter.getItemId(pos); // item the user is currently looking at
savedPositionOffset = cell == null ? 0 : cell.getTop(); // current item top offset
// Now we block listView drawing until after setSelectionFromTop() is called
final ListViewPredrawListener predrawListener = new ListViewPredrawListener(listView);
predrawListener.lock();
// We have no idea what changed between items and newItems, the only assumption
// that we make is that item with savedId is still in the newItems list
items = newItems;
notifyDataSetChanged();
// or for ArrayAdapter:
//clear();
//addAll(newItems);
listView.post(new Runnable() {
#Override
public void run() {
// Now we can finally unlock listView drawing
// Note that this code will always be executed
predrawListener.unlock();
int newPosition = ...; // Calculate new position based on the savedId
listView.setSelectionFromTop(newPosition, savedPositionOffset);
}
});
}
I play with menus using PopupWindow, which overlap EditText.
It works fine, except that my PopupWindow is overlapped by some items from EditText IME system (selection marks, Paste button).
My question is: how do I force z-ordering of my PopupWindow so that it appears above those decorations?
Here's image of what's happening. I need my PopupWindow (the menu) be drawn on top of everything, thus somehow tell WindowManager how to order windows.
Thanks.
Found anwer myself.
Those decorations are normal PopupWindow-s, managed by EditText.
Z-ordering of any Window is defined by WindowManager.LayoutParams.type, actually it defines purpose of Window. Valid ranges are FIRST_SUB_WINDOW - LAST_SUB_WINDOW for a popup window.
App typically can't change "type" of PopupWindow, except of calling hidden function PopupWindow.setWindowLayoutType(int) using Java reflection, and setting desired window type.
Result:
EDIT:
Code that does that:
Method[] methods = PopupWindow.class.getMethods();
for(Method m: methods){
if(m.getName().equals("setWindowLayoutType")) {
try{
m.invoke(getPopupWindow(), WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
}catch(Exception e){
e.printStackTrace();
}
break;
}
}
import android.support.v4.widget.PopupWindowCompat;
PopupWindowCompat.setWindowLayoutType(popupWindow, WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL);
public void compatibleSetWindowLayoutType(int layoutType) {
if (Build.VERSION.SDK_INT >= 23) {
setWindowLayoutType(layoutType);
} else {
try {
Class c = this.getClass();
Method m = c.getMethod("setWindowLayoutType", Integer.TYPE);
if(m != null) {
m.invoke(this, layoutType);
}
} catch (Exception e) {
}
}
}
Make are complement.
The PopupWindowCompat.setWindowLayoutType API must be called before show popWindow.
At the end of the HorizontalScrollView, a light appears to show that the scroll content has ended. Is there a way to change this color? It is appearing in my phone as a yellow one. I have already set the HorizontalScrollView's background color to the one that I desire, but this "end-of-scroll" light isn't the one I want.
EDIT:
I just noticed that this light appears due to the onOverScrollMode (since API level 9 - see this link). Is there a way to set OVER_SCROLL_NEVER and also keep the compatibility with the Eclair versions? Or even better: to set the color of this light (if it appears)?
Unfortunately there is no simple way to set the color for the OverScroll EdgeEffect.
To safely set OVER_SCROLL_NEVER and remain compatible with early SDK revs, you can introspect for the setOverScrollMode method and if found call it. (tested on 3.1 and 2.2)
setContentView(R.layout.main);
// find scroll view
HorizontalScrollView hscroll = (HorizontalScrollView)findViewById(R.id.hscroll);
try {
// look for setOverScrollMode method
Method setOverScroll = hscroll.getClass().getMethod("setOverScrollMode", new Class[] { Integer.TYPE } );
if (setOverScroll != null) {
try {
// if found call it (OVER_SCROLL_NEVER == 2)
setOverScroll.invoke(hscroll, 2);
} catch (InvocationTargetException ite) {
} catch (IllegalAccessException ie) {
}
}
} catch (NoSuchMethodException nsme) {
}
Although this question has already been answered, I wanted to add a couple of more ways that you can go about setting the overScrollMode attribute.
1) Create a "layout-v10" folder and put your alternate xml layout with the overScrollMode attribute set as desired.
2) If #1 would mean copying a ton of files and duplication, you can alternatively create a style for the HorizontalScrollView.
You can set the EdgeEffect color using reflection. The following will work from API 14+:
public static void setEdgeGlowColor(final HorizontalScrollView hsv, final int color) {
try {
final Class<?> clazz = HorizontalScrollView.class;
for (final String name : new String[] {
"mEdgeGlowLeft", "mEdgeGlowRight"
}) {
final Field field = clazz.getDeclaredField(name);
field.setAccessible(true);
setEdgeEffectColor((EdgeEffect) field.get(hsv), color);
}
} catch (final Exception ignored) {
}
}
public static void setEdgeEffectColor(final EdgeEffect edgeEffect, final int color) {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
edgeEffect.setColor(color);
return;
}
final Field edgeField = EdgeEffect.class.getDeclaredField("mEdge");
edgeField.setAccessible(true);
final Field glowField = EdgeEffect.class.getDeclaredField("mGlow");
glowField.setAccessible(true);
final Drawable mEdge = (Drawable) edgeField.get(edgeEffect);
final Drawable mGlow = (Drawable) glowField.get(edgeEffect);
mEdge.setColorFilter(color, PorterDuff.Mode.SRC_IN);
mGlow.setColorFilter(color, PorterDuff.Mode.SRC_IN);
mEdge.setCallback(null); // free up any references
mGlow.setCallback(null); // free up any references
} catch (final Exception ignored) {
}
}