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.
Related
I have a very odd problem with the bottom navigation buttons. Our device has for a reason unknown to us set ro.sf.hwrotation=270 in device properties list. With this settings everything works in portrait mode as intended (the boot logo/animation, system activities, our app, ...). What does not work is Miracast display which is, as you might have guessed it, incorrectly rotated.
So I have decided to look at it more closely and as the first step changed the property to ro.sf.hwrotation=0. Luckily this has fixed the Miracast issue, all system activities are still in portrait, our app ditto except this weird issue:
The buttons are gone. When rotated to landscape, again, everything is as it should except the buttons (now even stuck to the right side):
Can somebody give me a hint where to look? I can't see anything suspicious in the logs, no crash or exception.
Most likely the buttons are somehow weirdly rotated too but then again, why only the buttons? Is there something specific to them? If I revert the setting, everything is back to normal.
Although this is hardly a generic solution, it may help someone in the future: our device had tweaked not only the rotation setting but also ro.sf.lcd_density (changed from 160 to 188).
While experimenting I noticed that Overview (recent apps) button still somehow works, it's just hardly reachable (and not visible). So the button was there.
After yet another round of experiments and I noticed an innocent change of the density setting - and guess what, after reverting the value to 160 it worked again! For some reason we had decreased the LCD's width/height this way.
Of course, it comes with a price - now that we have increased LCD resolution, the device shows one button more and even in different order.
Accidentally, when I was about to start to live with different DPI/button arrangement I found this post: https://community.nxp.com/thread/384348#comment-831065. The author handles only one side of the display so this is a version to handle all four of them:
--- a/android4.2/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/android4.2/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
## -415,7 +415,20 ## public class NavigationBarView extends LinearLayout {
for (int i=0; i<4; i++) {
mRotatedViews[i].setVisibility(View.GONE);
}
- mCurrentView = mRotatedViews[rot];
+ //mCurrentView = mRotatedViews[rot];
+
+ if (rot == Surface.ROTATION_0 && mVertical) {
+ mCurrentView = mRotatedViews[Surface.ROTATION_90];
+ } else if (rot == Surface.ROTATION_90 && !mVertical) {
+ mCurrentView = mRotatedViews[Surface.ROTATION_180];
+ } else if (rot == Surface.ROTATION_180 && mVertical) {
+ mCurrentView = mRotatedViews[Surface.ROTATION_270];
+ } else if (rot == Surface.ROTATION_270 && !mVertical) {
+ mCurrentView = mRotatedViews[Surface.ROTATION_0];
+ } else {
+ mCurrentView = mRotatedViews[rot];
+ }
+
mCurrentView.setVisibility(View.VISIBLE);
mDeadZone = (DeadZone) mCurrentView.findViewById(R.id.deadzone);
After applying this patch I finally have a good solution - DPI is unchanged (i.e. 188), buttons are visible, HDMI is fixed and everything works as before.
So, I'm building fairly simple application for portrait mode only, and it will have vertical scrolling for content.
Content is basicly a MovieClip containing image as background and other minor elements (couple small MovieClips as buttons etc).
The problem is that I don't seem to find a smooth way to scroll my content. I would love it to be smooth and accurate, and I've tried numerous different ways for it.
For testing purposes the background I'm using (inside my content MovieClip) is 533x1250 jpg from library.
This is how I add my content mc and backround into it:
scaleXFactor = stage.stageWidth / 640; //my stage is 640x960
...
addChild(content); //which is new MovieClip()
var bg:bg1 = new bg1();
bg.x = p1bg.y = 0;
bg.width = 640 * scaleXFactor;
bg.scaleY = bg.scaleX;
content.width = bg.width;
content.height = bg.height;
content.addChild(bg);
For scrolling I assumed I'd be safe by just simply using MOUSE_DOWN & MOUSE_UP event listeners and basic ENTER_FRAME function like this:
mouseLastPos = stage.mouseY;
if(MouseUpVar == 1){
mousePosDiff = (mouseOriPosY-mouseLastPos);
}
content.y -= mousePosDiff*1.2;
mouseOriPosY = stage.mouseY;
if(MouseUpVar == 0){ //Smoothening which is allowed by MOUSE_UP
if(mousePosDiff > 0){
mousePosDiff -= 0.001;
}else if(mousePosDiff < 0){
mousePosDiff += 0.001;
}
}
if(content.y < (content.height - stage.stageHeight)*-1){
content.y = (content.height - stage.stageHeight)*-1;
}else if(content.y > 0){
content.y = 0;
}
In debug player it's as smooth as you can ever imagine, but in real device (Samsung Note4) it's very laggy and clumsy.
I've also tried freshplanet's script, which is not as laggy, but still a little clumsy, and messes up my scaleY for some reason.
And IScrollerComponent seems to have some issues on AIR, and I didn't get any visual elements on my screen with it.
Rendering settings or cacheAsBitmap didn't seem to have any effect on this.
How should this be done right? Is it normal that this 'small' image (533x1250) causes bad lag?
I really need to use AS3 and AIR for this, so I'm hoping to find a good and "simple" solution (yeah, that doesn't happen too often).
Thanks,
kaarto
Try using cacheAsBitmap=true on your content and scrollRect instead of moving your content.
simple question, is there ViewPager.PageTransformer that animates a page curl effect?
I've been looking everywhere, but I couldn't find it and wouldn't really know how to implement it myself...
Thanks in advance,
Cédric
I think #Cédric was right — it's probably not worth it.
So I managed to get this working, but there's a lot of ugliness in there.
There's some duct tape in the software architecture. Because page transformers only work on views, the app depends on using a custom layout that can handle drawing the page curl. So the transformer looks like this:
public static class PageCurlPageTransformer implements PageTransformer {
#Override
public void transformPage(View page, float position) {
Log.d(TAG, "transformPage, position = " + position + ", page = " + page.getTag(R.id.viewpager));
if (page instanceof PageCurl) {
if (position > -1.0F && position < 1.0F) {
// hold the page steady and let the views do the work
page.setTranslationX(-position * page.getWidth());
} else {
page.setTranslationX(0.0F);
}
if (position <= 1.0F && position >= -1.0F) {
((PageCurl) page).setCurlFactor(position);
}
}
}
}
To get a realistic page-turn reveal, you have to use Canvas.clipPath. I had trouble getting this to clip to the actual path when testing on an emulator. I had to resort to testing on the device to see clipPath work correctly. Even turning off hardware acceleration didn't work on the emulator. This gives me low confidence that this will look right on all devices all the time.
I just drew the curling corner with dispatchDraw in the custom layout. That's probably not the best place to do it. If I were taking more time, I would probably have a special decor view in the view pager and have that draw the curl.
You might notice that the paging in reverse is faster than paging forward. You might not like the animation speed in either forward or reverse. Too bad — ViewPager doesn't have any methods to tweak the fling speeds, so you get what you get. This is yet another limitation of using ViewPager for this sort of thing.
You can look at my project as a proof-of-concept that you can indeed get a page curl to work with a view pager and a page transformer. Hopefully this will give you what you need to implement it in your project.
Project is here: https://github.com/klarson2/PageCurlWithPageTransformer
Cheers
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);
I have the following problem. I have placed 30+ images in a HorizontalScrollView. I am correctly detecting onScroll() by overriding this method in HorizontalScrollView. The problem is I need to only load those images the user is near or there will be too many downloading/in memmory etc. So If user scrolls fast right now it starts downloading everything from start of scroll to end of scroll. This might be a hundred images and yet at end of scroll they are only next to say 4 or 5 images. How do I detect the stop of scrolling? and use this to kick off download?
EDIT: For slow scrolling it works perfect, but fast scrolling is the problem. If the scroll is slow then the behavior is perfect.
EDIT: What I am doing is updating any images that are nearby in onScroll but onScroll seems to get called many many times, and its difficult to determine if the images should be updated or not from inside onScroll. For example x=50, xPrev=49, xPrev=50, x=51 etc really for each pixel change onScroll gets called.
Thanks
Your images are moving in horizontal direction ,
what you need to do is find the velocity of moving images and based on it load images or skip them ,
get current time in onScrollChanged method save it to a variable and find difference with old time , if distance i.e. difference in horizontal x1 and x2 is more than a certain level say screen width (experiment with it ) , then calculate velocity if the velocity is below certain level load the images or otherwise ignore ,always load images when idle i.e. when velocity is zero.
Did not find anyway to detect end of scrolling but using a CountDownTimer seems to work:
I also needed to override HorizontalScrollView to add a callback IHorizontalScrollListener which is called inside onScrollView of HorizontalScrollView. viewGroupContainer is the container LinearLayout for the ImageViews. A scroll view can only contain directly one ViewGroup so I added all the images to the view group. The CountDownTimer is reference maintained in the activity.
hzScrollView.setScrollListener(new IHorizontalScrollListener(){
#Override
public void onScrollChanged(int l, int t, int oldl, int oldt) {
Log.i(TAG, "onScrollChanged to x="+l+" y="+t);
Log.i(TAG, "onScrollChanged from x="+oldl+ " y="+oldt);
if (scrollUpdateTimer == null)
{
scrollUpdateTimer = new CountDownTimer(30000, 1000){
#Override
public void onFinish() {
scrollUpdateTimerRunning = false;
}
#Override
public void onTick(long interval) {
Log.d(TAG, "onTick updateImageViews");
updateImageViews(hZScrollView, viewGroupContainer);
}};
}
if (scrollUpdateTimerRunning == false)
{
scrollUpdateTimerRunning = true;
scrollUpdateTimer.start();
}
}});
}