I'm using ListViewDraggingAnimation by DevBytes, but it seems broken on Android Lollipop developer preview 2 (LPX13D). When I drag a row over other rows, those rows will disappear and become no longer clickable (see below). I tried disabling hardware acceleration for the listview but it didn't have any effect.
Has anyone experienced the same issue? Any hints? Thanks :)
I found the problem. It came from this flag. StableArrayAdapter.hasStableId.
It fix all problem from this view on Lollipop.
#Override
public boolean hasStableIds()
{
return android.os.Build.VERSION.SDK_INT < 20;
}
To be honest, I don't what causes the problem, but this fix makes no visual errors anymore on any version. And because all that was changed is just visibility of view, I believe it shouldn't create any new functional problems.
Replace this code in function handleCellSwitch() in class DynamicListView:
mobileView.setVisibility(View.VISIBLE);
switchView.setVisibility(View.INVISIBLE);
for
if (android.os.Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.KITKAT){
mobileView.setVisibility(View.VISIBLE);
switchView.setVisibility(View.INVISIBLE);
} else{
mobileView.setVisibility(View.INVISIBLE);
switchView.setVisibility(View.VISIBLE);
}
I tried tinkering with the code and in the end I found a way how to make it display as it was before. But fixing the problem for Lollipop made the same problem appear on KitKat and previous versions instead. So I applied the fix only for android versions higher than KitKat.
My guess is that these two views gets exchanged in the process for some reason on the new Lollipop version. In effect, one of the views gets always displayed while the other one gets always hidden. Would be nice to know where and what was changed in the lollipop android code though...
The proper solution is to turn the visibility of that view back on before it's recycled and then turn it back off before it's drawn.
((BaseAdapter) getAdapter()).notifyDataSetChanged();
mDownY = mLastEventY;
final int switchViewStartTop = switchView.getTop();
mobileView.setVisibility(View.VISIBLE);
switchView.setVisibility(View.INVISIBLE);
updateNeighborViewsForID(mMobileItemId);
final ViewTreeObserver observer = getViewTreeObserver();
observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
public boolean onPreDraw() {
observer.removeOnPreDrawListener(this);
replace with,
mobileView.setVisibility(VISIBLE);
((BaseAdapter) getAdapter()).notifyDataSetChanged();
mDownY = mLastEventY;
final int switchViewStartTop = switchView.getTop();
updateNeighborViewsForID(mMobileItemId);
final ViewTreeObserver observer = getViewTreeObserver();
observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
public boolean onPreDraw() {
observer.removeOnPreDrawListener(this);
View mobileView = getViewForID(mMobileItemId);
if (mobileView != null) mobileView.setVisibility(INVISIBLE);
The stableid solution is wrong. The IDs are stable and telling it that they aren't is in error. It simply kluges the system into recycling the views the old way. Which was in fact a bad way of doing it and was fixed in lollipop. This is a much more correct solution. Rather than predicting how the .notifyDataSetChanged will affect the views and turning on and off the proper elements given whatever version you're using. This one will turn on the visibility in all cases. Then in the predraw listener to find the view again and turns it back invisible, before it's drawn and after it's in its proper state.
And it doesn't matter, if that view is the same one or not, or some future version with some different way of doing it, or a bad adapter that doesn't actually recycle the views, etc. Stable Ids or non-stable ids (flagged not stable, they still have to be actually stable, but that's easily fixable), it's the way to properly do it and future proofed.
The problem here is that which recycled view you're going to get isn't actually clear (they are half trash recycled views after all), not things you can safely store properties in. The behavior was changed in Lollipop to keep stable views actually stable if they can, you could check in the adapter function and the recycled view will likely already have your data because it will give you the same view back again maximally often. Which is something you want, which is why Lollipop does it that way.
The problem here is that the code says mobileView should be made to be visible (the one you're moving), and the switchView should be made invisible (the one you're switching with). But, these are going to be recycled so they are technically ambiguous which one you'll get back, and depends entirely on how the system is recycling the views, and it's completely allowed to change that behavior for the better, and did.
Ps. I bother to check for null because I personally swap the views at the midway point and it can sometimes end up being null if you hit things just right.
int deltaYTotal = (mHoverCellOriginalBounds.bottom + mHoverCellOriginalBounds.top) / 2
+ mTotalOffset + deltaY;
...
boolean isBelow = (belowView != null) && (deltaYTotal > belowView.getTop());
boolean isAbove = (aboveView != null) && (deltaYTotal < aboveView.getBottom());
You need to visible the mobileView and switchView before adapter notify.
This is work for me.
mobileView.setVisibility(View.VISIBLE);
switchView.setVisibility(View.INVISIBLE);
((BaseAdapter) getAdapter()).notifyDataSetChanged();
mDownY = mLastEventY;
final int switchViewStartTop = switchView.getTop();
updateNeighborViewsForID(mMobileItemId);
Related
This question is probably the same as this one, but since none of its answers really solve the problem, I'll ask again.
My app has a TextView that occasionally will display very long URLs. For aesthetic reasons (and since the URLs contain no spaces), the desirable behaviour would be to fill each line completely before jumping to the next one, something like this:
|http://www.domain.com/som|
|ething/otherthing/foobar/|
|helloworld |
What happens instead, is the URL being broke near the bars, as if they were spaces.
|http://www.domain.com/ |
|something/otherthing/ |
|foobar/helloworld |
I tried extending the TextView class and adding a modified version of the breakManually method (found here) to trick the TextView and do what I need, calling it on onSizeChanged (overridden). It works fine, except for the fact that the TextView is inside a ListView. When this custom TextView is hidden by the scrolling and brought back, its content goes back to the original breaking behaviour, due to the view being re-drawn without onSizeChanged being called.
I was able to work-around this problem by calling breakManually inside onDraw. This presents the expected behaviour at all times, but at a high performance cost: since onDraw is called whenever the ListView is scrolled and the breakManually method isn't exactly "lightweight", the scrolling gets unacceptably laggy, even on a high-end quad-core device.
Next step was to crawl through the TextView source code, trying to figure where and how it splits the text, and hopefully override it. This was a complete failure. I (a newbie) spent the whole day fruitlessly looking at code I mostly couldn't understand.
And that brings me here. Can someone please point me the right direction about what I should override (assuming that it's possible)? Or maybe there is a simpler way of achieving what I want?
Here's the breakManually method I mentioned. Due to the use of getWidth(), it only works if called after the view is measured.
private CharSequence breakManually (CharSequence text) {
int width = getWidth() - getPaddingLeft() - getPaddingRight();
// Can't break with a width of 0.
if (width == 0) return text;
Editable editable = new SpannableStringBuilder(text);
//creates an array with the width of each character
float[] widths = new float[editable.length()];
Paint p = getPaint();
p.getTextWidths(editable.toString(), widths);
float currentWidth = 0.0f;
int position = 0;
int insertCount = 0;
int initialLength = editable.length();
while (position < initialLength) {
currentWidth += widths[position];
char curChar = editable.charAt(position + insertCount);
if (curChar == '\n') {
currentWidth = 0.0f;
} else if (currentWidth > width) {
editable.insert(position + insertCount , "\n");
insertCount++;
currentWidth = widths[position];
}
position++;
}
return editable.toString();
}
To everyone who bothered reading this, thanks for your time.
If you're not using a monospace font, then in some cases it's not even possible to align it very well. Since you have no whitespaces in a URL, it's not likely that a Justify-like alignment will solve the problem. I suggest that you use a monospace font for that particular TextView. Then, decide on a fixed number of characters per line and break the string to display with "\n" after those many characters.
This isn't answering your question but it's the smoothest way possible, I guess.
Today I noticed that when you do a quick double drag in a ListView, the list will go to the end or the beginning of the list. It appears that this only happens in Samsung phones.
This has become an issue because I am adding JazzyListView animation effects to the elements on the list and they crash whenever this quick movement happens. (NullPointerException whenever the ListView.layoutChildren method is invoked)
Maybe I will need to modify the library, or is it any way to disable this ListView behavior?
Well this effect is called "DOUBLE FLING", it appears in logcat whenever I do it, unfortunately in AbsListView the only thing you can do about flinging is setting friction and velocity scale. If someone knows how to disable the effect it would be great (However that may not be the best solution for this problem, as the users may be familiar with that behavior)
What I did (my quick + dirty solution) was to modify the JazzyListView library, specifically JazzyHelper class, only validating that the item is not null before applying animation:
while (firstVisibleItem + indexAfterFirst < mFirstVisibleItem) {
View item = view.getChildAt(indexAfterFirst);
if(item != null) {
doJazziness(item, firstVisibleItem + indexAfterFirst, -1);
}
indexAfterFirst++;
}
int indexBeforeLast = 0;
while (lastVisibleItem - indexBeforeLast > mLastVisibleItem) {
View item = view.getChildAt(lastVisibleItem - firstVisibleItem - indexBeforeLast);
if(item != null) {
doJazziness(item, lastVisibleItem - indexBeforeLast, 1);
}
indexBeforeLast++;
}
I believe that I should modify something about instance property mFirstVisibleItem, else the fastscrolling removes the animation on displaying items. However, my solution works for anyone who wants to quickly solve this issue.
I'm a little bit stuck with this one - first and foremost, the following link has been useful however I've come up with a bit of an issue with visibility:
The link: Check view visibility
I have a scroll view (parent) and a number of sub-views (LinearLayout -> TableLayout) etc. There are a number of items I set to View.GONE within the XML (android:visibility="gone").
I have some simple code to determine whether it is visible or not using getVisibility() however when I set the item to View.VISIBLE and try to immediately getDrawingRect() I get a Rect with zeros across the board. Any further click gets the correct coordinates.
Now this could be because the view has never been drawn (as defined in the XML) causing it to return no coordinates however I do set View.VISIBLE before trying to determine screen visibility. Could it be that I need to get some kind of callback from say the onDraw()? or perhaps set the view visibility of hidden items within code. A bit annoying ;(
Some code:
Rect scrollBounds = new Rect();
scroll.getHitRect(scrollBounds);
Rect viewBounds = new Rect();
if (view.getVisibility() == View.GONE) {
view.setVisibility(View.VISBLE)
viewBounds.getDrawingRect(viewBounds);
if (!Rect.intersects(scrollBounds, viewBounds) {
// do somthing
}
}
Layouts area as follows:
ScrollView
LinearLayout
TableLayout
Button
HiddenView
Of course, it's highly likely I'm going about this the wrong way altogether - basically I just want to make sure that the scrollview positions itself so the view that has become visible can be seen in it's entirety.
If any other information is required, let me know!
Ok so thanks to OceanLife for pointing me in the right direction! There was indeed a callback required and ViewTreeObserver.OnGlobalLayoutListener() did the trick. I ended up implementing the listener against my fragment class and picked it up where I needed it. Thanks for the warning too regarding the multiple calls, I resolved this using the removeOnGlobalLayoutListener() method - works a charm.
Code:
...
// vto initialised in my onCreateView() method
vto = getView().getViewTreeObserver();
vto.addOnGlobalLayoutListener(this);
...
#Override
public void onGlobalLayout() {
final int i[] = new int[2];
final Rect scrollBounds = new Rect();
sView.getHitRect(scrollBounds);
tempView.getLocationOnScreen(i);
if (i[1] >= scrollBounds.bottom) {
sView.post(new Runnable() {
#Override
public void run() {
sView.smoothScrollTo(0, sView.getScrollY() + (i[1] - scrollBounds.bottom));
}
});
}
vto.removeOnGlobalLayoutListener(this);
}
Just got to do some cleaning up now ...
So, if I am reading this right the issue you are having is that you want to find out the dimensions of some view in your layout to find out whether you have an intersection with the parent ScrollView.
What I think you are seeing (as you alluded to) is the instruction to draw the view being dispatched, and then in some sort of race-condition, the renderer resolving the measurements for the layout and the actual render where view objects get real sizes. One way to find out what sort of dimensions a view has on screen is to use the layout tree-listener. We use this observer to resolve a screen's dimensions when leveraging the Google Charts API, which requires a pixel width and height to be defined... which of course on Android is probably the biggest problem facing developers. So observe (pun intended);
final ViewTreeObserver vto = chart.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
/**
* #see android.view.ViewTreeObserver.OnGlobalLayoutListener#onGlobalLayout()
*/
#SuppressWarnings("deprecation")
#Override
public void onGlobalLayout() {
if (view.getVisibility() == View.GONE) {
view.setVisibility(View.VISBLE)
viewBounds.getDrawingRect(viewBounds);
if (!Rect.intersects(scrollBounds, viewBounds) {
// do something
}
}
}
});
Word of warning the onGlobalLayout will get called multiple times as the renderer closes in on the final solution. The last call is the one we take.
This will provide you with a mechanism for performing actions on a drawn view like getting a view component's width and height. Hope that helps you out.
Aside: The clever chaps over at Square have designed a FEST hamcrest library that will allow you to write a test for this alongside Robotium. Check it out.
Google suggests that the following is not that uncommon a question: having loaded something into a Flash stage using a Loader, I want to resize it. However, if you do this before the content is loaded, resizing the image causes it to disappear.
The proposed solution is usually to use an Event listener for Event.COMPLETE. Here's my code:
public function FlixelTest()
{
super();
// support autoOrients
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
myLoader = new Loader();
myLoader.x = (stage.fullScreenWidth-640)/2;
myLoader.y = (stage.fullScreenHeight-480)/2;
addChild(myLoader);
var url:URLRequest = new URLRequest("stuff.swf");
myLoader.load(url);
myLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, loadProdComplete);
}
function loadProdComplete(e:Event):void{
myLoader.height = 480;
myLoader.width = 640;
}
According to every posting I can find so far online, this solution should work. When the event fires, the Loader is done, and can be resized. However, it doesn't. Commenting out the lines that modify .height and .width cause the SWF to appear, uncommenting them and running again and the SWF never loads.
Could anything else be interfering here? This is using FlashBuilder to construct an Actionscript3 -> Android project.
EDIT - The solution here doesn't appear to work either: Problem resizing loader after loading swf
UPDATE - I have a working, and horrific, solution that is as followed:
function loadProdComplete(e:Event):void{
if(myLoader.width != 0){
myLoader.width = 640;
myLoader.content.width = 640;
}
else{
timer = new Timer(500);
timer.addEventListener(TimerEvent.TIMER, timertick); // there should be a comma here but yahoo replaces it with ...
timer.start();
}
}
function timertick(e:TimerEvent){
timer.stop();
stage.dispatchEvent(new Event(Event.COMPLETE));
}
It basically uses the content's width to see if it's finished loading. If it hasn't, it waits a half second and refires the COMPLETE event. This actually helps display it (although the width hasn't been adjusted, I assume that's a separate issue) - I can't believe this is the only way to get it working...
While not strictly a solution, the way I've circumvented this is by not resizing it at all - I am instead cropping an area over the window so all that's seen is the section I wanted to display.
function loadProdComplete(e:Event):void{
var gameMask : Shape = new Shape;
gameMask.graphics.beginFill(0xffcc00);
gameMask.graphics.drawRect(myLoader.x,myLoader.y,640,480);
gameMask.graphics.endFill();
myLoader.content.mask = gameMask;
}
Since the mask would've been necessary for me anyway to hide off-stage clutter, this solved two problems at once.
I won't accept this as the answer in case anyone has any alternative insights, but if anyone comes across this question, this is a posisble solution.
I am using multiple animations and playing animations one after other. Currently using onAnimationEnd() to play animations one after other. In case of onTouch, I need to stop the animation and need to set different bitmap to imageview in touch location. Currently using below code but facing problems in case of clearAnimation().
#Override
public boolean onTouch(View v, MotionEvent event) {
switch(event.getAction()) {
case MotionEvent.ACTION_UP:
imageArray[g_animCount - 1].clearAnimation();
break;
default:
break;
}
return true; // indicate event was handled
}
#Override
public void onAnimationEnd(Animation animation) {
layout.removeView(imageArray[g_animCount - 1]);
if ( (g_animCount < count))
{
startNextAnimation();
}
else
{
g_animCount = 0;
isBegin = false;
}
}
Problems and queries:
After clear animation, I could see image again at the beginning location, how to keep it at touch location ? tried setFillAfter(true) but no use.
How to define onAnimationEnd() in case to play animations one after other ? do we need to remove imageview?
Without clearAnimation() I do not have any problems but it does not solve my problem. Kindly correct my code.
1) to keep the ImageView to the new location you have to specify the new coordinates.
You can do it in this way:
MarginLayoutParams l = new MarginLayoutParams(v.getLayoutParams());
l.leftMargin = left;
l.topMargin = top;
v.setLayoutParams(l);
v is your ImageView, left and top are the coordinates (x,y) to place the view on the screen. The parent should be a RelativeLayout.
2) it's better to start all the animation in sequence avoid calling new start in the callback methods (onAnimationEnd, etc). You don't need to remove the view if you have to use it later in the flow
My reply may be a bit out of date, but better later than never.
I also noticed this nasty bug: when you call cancelAnimation, the AnimationListener.onAnimationEnd is called, however the view stays and there is no "legal" ways to hide it (tried remove view from layout, set visibility to View.INVISIBLE, even negative marginTop and marginBottom - nothing works). The bug is present with Andoid 2.3 and Android 4.0. It doesn't turn up with KitKat, presumably has been fixed.
Here's the good news. I've found a work around with ImageView: in AnimationListener.onAnimationEnd I say animationView.setImageDrawable(null) and the view disappears. Hopefully this will help someone.