I have a property animation set for the alpha channels of several views which is not working as I think it should. Essentially I have 3 TextView derivatives, a TextView and two Views which I want to initially be hidden, set their data when it comes in and then animate them in. I do this with several other views in the layout and occasionally they have the same problem although it is very infrequent. Most of the time the TextView and one of the Views simple does not appear even though the data for the TextView is valid according to its Logcat output. It is always the same two views and they do rarely show up but not an any predictable manner I have been able to find. The section of the layout file and all the animation code follows. Please note that as per this question, I have set the visibility to invisible in the layout and set it to visible right before starting the animation.
EDIT
Calling `View.bringToFront() on the views which are having trouble has no visible effect. Also, not executing the animation but instead just changing the visibility of the views from invisible to visible does not show them. Removing the visibility change code and android:visibility="invisible" tag from the layout also does not make the view display.
THE LAYOUT
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:qwerjk="http://schemas.android.com/apk/res/tenkiv.app"
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="8dp"
android:paddingLeft="8dp"
android:paddingRight="8dp" >
<tenkiv.view.widget.MagicTextView
android:id="#+id/hourMinuteValueTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_toLeftOf="#+id/secondValueTv"
android:lines="1"
android:textSize="77sp"
android:visibility="invisible"
qwerjk:strokeColor="#color/light_text_outline"
qwerjk:strokeJoinStyle="miter"
qwerjk:strokeWidth="1.8" />
<tenkiv.view.widget.MagicTextView
android:id="#+id/secondValueTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="#+id/hourMinuteValueTv"
android:layout_toLeftOf="#+id/amPmValueTv"
android:textSize="39sp"
android:visibility="invisible"
qwerjk:strokeColor="#color/light_text_outline"
qwerjk:strokeJoinStyle="round"
qwerjk:strokeWidth="1.3" />
<tenkiv.view.widget.MagicTextView
android:id="#+id/amPmValueTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="#+id/hourMinuteValueTv"
android:layout_alignParentRight="true"
android:textSize="31sp"
android:visibility="invisible"
qwerjk:strokeColor="#color/light_text_outline"
qwerjk:strokeJoinStyle="round"
qwerjk:strokeWidth="1.3" />
<tenkiv.view.widget.MagicTextView
android:id="#+id/dateValueTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_below="#+id/hourMinuteValueTv"
android:textSize="19sp"
android:visibility="invisible"
qwerjk:strokeColor="#color/light_text_outline"
qwerjk:strokeJoinStyle="round"
qwerjk:strokeWidth="0.9" />
<View
android:id="#+id/lineTimeConditionSepView"
android:layout_width="290dp"
android:layout_height="3dp"
android:layout_alignParentRight="true"
android:layout_below="#id/dateValueTv"
android:layout_marginTop="4dp"
android:background="#color/holo_text"
android:visibility="invisible" />
<View
android:id="#+id/lineTimeConditionSepViewOutline2"
android:layout_width="290dp"
android:layout_height="1dp"
android:layout_alignBottom="#id/lineTimeConditionSepView"
android:layout_alignLeft="#id/lineTimeConditionSepView"
android:layout_marginTop="0.5dp"
android:background="#color/location_area_dark_green"
android:visibility="invisible" />
---THE REST OF THE LAYOUT---
THE METHODS IN MY FRAGMENT TO ANIMATE IT
public void onUpdateClock(final String time, final String seconds, final String ampm, final String date) {
Activity activity = getActivity(); //Used to make sure we dont update the clock with the activity dead
if (activity != null) {
if (mHourMinuteValue == null) { //These are all used together so this should be safe
mHourMinuteValue = (MagicTextView) activity.findViewById(R.id.hourMinuteValueTv);
mSecondValue = (MagicTextView) activity.findViewById(R.id.secondValueTv);
mAmPmValue = (MagicTextView) activity.findViewById(R.id.amPmValueTv);
mDateValue = (TextView) activity.findViewById(R.id.dateValueTv);
mTimeConditionsLine = activity.findViewById(R.id.lineTimeConditionSepView);
mTimeConditionsOutline2 = activity.findViewById(R.id.lineTimeConditionSepViewOutline2);
mTimeInAnim = createAnimation(CLOCK_IN);
}
}
if (mHourMinuteValue == null) {
Log.e(TAG, "Clock text views were null!");
return;
}
uiHandler.post(new Runnable() {
public void run() {
mHourMinuteValue.setText(time);
mSecondValue.setText(seconds);
if (ampm != null) {
mAmPmValue.setText(ampm);
} else {
mAmPmValue.setText(ClockUICallbacks.EMPTY_AMPM);
}
mDateValue.setText(date);
mDateValue.bringToFront();
if (mFadeInClock) {
setClockVisibility(true);
mTimeInAnim.start();
mFadeInClock = false;
}
}
});
private void setClockVisibility(boolean visible) {
int flag;
if (visible) {
flag = View.VISIBLE;
} else {
flag = View.INVISIBLE;
}
mHourMinuteValue.setVisibility(flag);
mSecondValue.setVisibility(flag);
mAmPmValue.setVisibility(flag);
mDateValue.setVisibility(flag);
mTimeConditionsLine.setVisibility(flag);
mTimeConditionsOutline2.setVisibility(flag);
}
private AnimatorSet createAnimation(int type) {
AnimatorSet set = new AnimatorSet();
switch (type) {
case CLOCK_IN:
ValueAnimator hourMinInAnim = ObjectAnimator.ofFloat(mHourMinuteValue, "alpha", INVISIBLE, OPAQUE);
hourMinInAnim.setDuration(FADE_IN_DURATION);
ValueAnimator secondInAnim = ObjectAnimator.ofFloat(mSecondValue, "alpha", INVISIBLE, OPAQUE);
secondInAnim.setDuration(FADE_IN_DURATION);
ValueAnimator amPmInAnim = ObjectAnimator.ofFloat(mAmPmValue, "alpha", INVISIBLE, OPAQUE);
amPmInAnim.setDuration(FADE_IN_DURATION);
ValueAnimator dateInAnim = ObjectAnimator.ofFloat(mDateValue, "alpha", INVISIBLE, OPAQUE);
dateInAnim.setDuration(FADE_IN_DURATION);
ValueAnimator timeLine1InAnim = ObjectAnimator.ofFloat(mTimeConditionsLine, "alpha", INVISIBLE, OPAQUE);
timeLine1InAnim.setDuration(FADE_IN_DURATION);
ValueAnimator timeLine2InAnim = ObjectAnimator.ofFloat(mTimeConditionsOutline2, "alpha", INVISIBLE, OPAQUE);
timeLine2InAnim.setDuration(FADE_IN_DURATION);
set.playTogether(hourMinInAnim, secondInAnim, amPmInAnim, dateInAnim, timeLine1InAnim, timeLine2InAnim);
return set;
/*SOME OTHER ANIMATIONS*/
}
return null;
}
Any other information which might be needed please let me know.
Thanks, Jared
It turns out that this was caused by a mistake in creating the layouts for two different fragments in the activity. If you look in the layout there is a MagicTextView with id dateValueTv. Well this same id existed in another layout within the activity and since both were using the activity findViewById() method to get the references sometimes they found the right view and sometimes not. I discovered this because my text view in the other fragment had the data that this problem text view should have had. All I had to do was change their id's. I am aware that I could do it based on getView().findViewById(). If this is the best practice I would love to hear comments on it but by checking if the activity is null from getActivity() and getting the views from there I have a convenient way of making sure my background thread doesn't post messages to the ui handler if the activity doesn't exist (such as during rotation).
Related
Ok... here's my situation.
I have a carousel of images in a HorizontalScrollView - which contains a LinearLayout - in my Activity, like so:
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/slider"
android:scrollbars="none" >
<LinearLayout
android:id="#+id/carousel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
/>
</HorizontalScrollView>
I have a TypedArray, loop through it, and on each run, set these images programatically, add a ClickListener and a Tag, and add this ImageView to the LinearLayout (set in my Activity Layout), like so:
// Get the array
final TypedArray carouselArray = getResources().obtainTypedArray(R.array.carousel_array);
// Populate the Carousel with item
for (int i = 0 ; i < carouselArray.length() ; ++i) {
// Image Item
ImageView outerImage;
// Set the image view resource
if(i == 0) {
outerImage.setImageResource(R.drawable.toy_filter_clear);
}
else {
outerImage.setImageResource(carouselArray.getResourceId(i, -1));
}
// Set Touch Listener
outerImage.setOnTouchListener(this);
final String prepend = "CAROUSEL_";
final String index = String.valueOf(i);
final String tag = prepend.concat(index);
outerImage.setTag(tag);
/// Add image view to the Carousel container
mCarouselContainer.addView(outerImage);
}
But now, I just found out that I have to programatically add a second image to sit inside/on top of the first image at particular coordinates (damn you UI ppl!). I need these to be considered the same image/view essentially, so need to pack them together inside of a layout, I am assuming. So I have made a layout file, like so:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/carousel_item"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:id="#+id/carousel_outer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:src="#drawable/toy_filter_normal"
/>
<ImageView
android:id="#+id/carousel_inner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="#+id/carousel_outer"
android:layout_alignRight="#+id/carousel_outer"
android:layout_marginBottom="10dp"
android:layout_marginRight="5dp"
android:src="#drawable/thumb_nofilter"
/>
</RelativeLayout>
This has the proper positioning, and the default images set on it. So what I want to be able to do is to reach into the Layout file, grab the ImageViews by their ID, overwrite the image if necessary, and then add that RelativeLayout to my LinearLayout at the end of my loop... sounds easy enough, right ?
My first attempt was to do it like this :
RelativeLayout item = (RelativeLayout) findViewById(R.id.carousel_item);
ImageView outerImage = (ImageView) item.findViewById(R.id.carousel_outer);
ImageView innerImage = (ImageView) item.findViewById(R.id.carousel_inner);
... but that gives me a NullPointer on the ImageView...So then I tried to inflate the RelativeLayout first, like this:
LayoutInflater inflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.carousel_item_layout, null);
ImageView outerImage = (ImageView) view.findViewById(R.id.carousel_outer);
ImageView innerImage = (ImageView) view.findViewById(R.id.carousel_inner);
This gets rid of the NPE's, and (apparently) let's the images be set properly like so:
if(i == 0) {
outerImage.setImageResource(R.drawable.toy_filter_clear);
innerImage.setImageResource(0);
}
else {
outerImage.setImageResource(R.drawable.toy_filter_normal);
innerImage.setImageResource(carouselArray.getResourceId(i, -1));
}
but when I try to add the outerImage ImageView back to the LinearLayout, I get an NPE there:
mCarouselContainer.addView(outerImage);
More to the point, I don't want to add ONLY the one ImageView to the LinearLayout/HorizontalScrollView - I want to somehow pack the resulting images back into the RelativeLayout and add the whole thing back into my LinearLayout... but, it is worth mentioning, that this also gives me an NPE.
What is a guy to do ? Any thoughts appreciated...
Ok... Wow, thanks SO Code Monkey!
I managed to fix this with a one line fix, by adding the inflated View to the LinearLayout instead of the ImageView or the RelativeLayout (which wasn't doing anything), like so:
mCarouselContainer.addView(view);
Don't know why I hadn't tried that before, but I was unclear on whether as it's children were being updated if it would reflect the parent, so to speak... now I know it was.
I'm gonna keep the question up, as I think it's helpful... ?
So, I have a Layout that contains a Button and an ImageView. When you press the button the ImageView should slide out from the button like I just pulled down a rolldown curtain (bushing other views below it down). Basically what the image below show. When you press the button again the ImageView should, unlike the gif, smoothly animates up again.
.
Using this SO question I've managed to animate the height from 0 to full size but in the wrong direction. I set the scaleType to "Matrix" and the default behaviour when setting the height is to show the part from the top down to [height].
For the animation I'll need the opposite. So if I would set the height to 50dp it would show the bottom 50dp. Then I can move the ImageView down at the same time it's being revealed, thus giving the rolldown curtain effect.
I've looked throught all the different layout and view options and found nothing that seems to do this. So I'm guessing I need to specify the transformation matrix. I looked through the android.graphics.Matrix class but it's a little but too complicated for me. I simply have no idea how to use it.
If there is another, easier, way to do this then that would be fantastic but if not then I really need help with the matrix.
I'm also including the code here:
The Rolldown View XML
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/sliding_accordion"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:src="#drawable/acc_image"
android:contentDescription="#string/accord"
android:scaleType="matrix"
android:layout_below="#+id/acc_button"
android:layout_marginTop="-10dp"
android:layout_centerHorizontal="true"/>
<Button
android:id="#+id/acc_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
The implementation in code.
(Note, the MyCustomAnimation class is a copy-paste version of the class found here)
//Called from all constructors
private void create()
{
final Context context = getContext();
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
RelativeLayout layout = (RelativeLayout) inflater.inflate(R.layout.widget_accordion, this, false);
final Button theButton = (Button) layout.findViewById(R.id.topic_button);
final ImageView accordionView = (ImageView) layout.findViewById(R.id.sliding_accordion);
accordionView.setVisibility(INVISIBLE);
theButton.setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v)
{
if (accordionView.getVisibility() == View.VISIBLE)
{
MyCustomAnimation a = new MyCustomAnimation(accordionView, 1000, MyCustomAnimation.COLLAPSE);
height = a.getHeight();
accordionView.startAnimation(a);
}
else
{
MyCustomAnimation a = new MyCustomAnimation(accordionView, 1000, MyCustomAnimation.EXPAND);
a.setHeight(height);
accordionView.startAnimation(a);
}
}
});
this.addView(layout);
}
This took a long time perfect. But I managed to do it after a lot of experimenting.
I animate the margins of the drawer but because of the unexpected behavior of negative margins the button that opens the drawer can not be positioned on top.
When the drawer is closed the XML looks like so:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/accordion"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.animationtest.drawer.Drawer
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/topic_drawer"
android:layout_centerHorizontal="true"
android:layout_marginTop="0dp"
android:visibility="invisible"/>
<com.animationtest.drawer.Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/topic_btn"
android:layout_marginTop="58dp"/>
</RelativeLayout>
Then when the button is pressed the top_margin of the drawer is increased until it has come to whatever position is needed (in this case drawerHeight - someOffset).
I used android.view.animation.Animation to animate the widget my applyTransformation function looks something like this (Note that mLayoutParams are the drawer params):
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
int valueDifference = Math.abs(startValue - endValue);
float valueChange = interpolatedTime * valueDifference;
if(currentState.equals(State.COLLAPSED)) {
// is closed and I want to open it
mLayoutParams.topMargin = Math.round(interpolatedTime * valueDifference);
}
else {
// is opened and I want to close it
mLayoutParams.topMargin = valueDifference - Math.round(interpolatedTime * valueDifference);
}
drawerView.requestLayout(); //this is my drawer
}
Finally, to hide the top of the drawer as it moves, I overrode my DrawerView's dispatchDraw method to looks like so:
#Override
protected void dispatchDraw(Canvas canvas) {
float height = getHeight();
float top = height - ((LayoutParams) getLayoutParams()).topMargin;
Path path = new Path();
RectF rectF = new RectF(0.0f, top, getWidth(), height);
path.addRoundRect(rectF, 0.0f, 0.0f, Path.Direction.CW);
canvas.clipPath(path);
super.dispatchDraw(canvas);
}
One final note:
Because of the Button's position one would need to set the widgets margin as a negative number for it to align correctly in a list or layout. In this case it would have to be -58dp.
I have two LinearLayout views that contain a number of edit texts and checkboxes for entering user information (name, email address etc). When a validation fails on one of these fields a gone textview is displayed showing the validation error.
I have enclosed the two layouts within a ViewSwitcher and I animate between the two views using the ObjectAnimator class. (Since the code needs to support older versions of Android I am actually using the nineoldandroids backwards compatibility library for this).
The bulk of the work is performed in my switchToChild method.
If I flip the views more than twice then I start to run into strange errors.
Firstly although the correct child view of the view animator is displayed it seems that the other view has focus and I can click on the views beneath the current one. I resolved this issue by adding a viewSwitcher.bringChildToFront at the end of the first animation.
When I do this however and perform a validation on the 2nd view the "gone" view that I have now set to visible is not displayed (as if the linearlayout is never being re-measured). Here is a subset of the XML file:
<ScrollView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/TitleBar"
android:scrollbarAlwaysDrawVerticalTrack="true"
android:scrollbarStyle="outsideOverlay"
android:scrollbars="vertical" >
<ViewSwitcher
android:id="#+id/switcher"
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
<LinearLayout
android:id="#+id/page_1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<!-- Lots of subviews here -->
<LinearLayout
android:id="#+id/page_2"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
And this is the main method for flipping between the views:
private void switchToChild(final int child) {
final ViewSwitcher viewSwitcher = (ViewSwitcher) findViewById(R.id.switcher);
if (viewSwitcher.getDisplayedChild() != child) {
final Interpolator accelerator = new AccelerateInterpolator();
final Interpolator decelerator = new DecelerateInterpolator();
final View visibleView;
final View invisibleView;
switch (child) {
case 0:
visibleView = findViewById(R.id.page_2);
invisibleView = findViewById(R.id.page_1);
findViewById(R.id.next).setVisibility(View.VISIBLE);
findViewById(R.id.back).setVisibility(View.GONE);
break;
case 1:
default:
visibleView = findViewById(R.id.page_1);
invisibleView = findViewById(R.id.page_2);
findViewById(R.id.back).setVisibility(View.VISIBLE);
findViewById(R.id.next).setVisibility(View.GONE);
break;
}
final ObjectAnimator visToInvis = ObjectAnimator.ofFloat(visibleView, "rotationY", 0f, 90f).setDuration(250);
visToInvis.setInterpolator(accelerator);
final ObjectAnimator invisToVis = ObjectAnimator.ofFloat(invisibleView, "rotationY", -90f, 0f).setDuration(250);
invisToVis.setInterpolator(decelerator);
visToInvis.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator anim) {
viewSwitcher.showNext();
invisToVis.start();
viewSwitcher.bringChildToFront(invisibleView); // If I don't do this the old view can have focus
}
});
visToInvis.start();
}
}
Does anyone have any ideas? This is really confusing me!
It looks like the best solution was not to use a view switcher at all but instead to effectively create my own one by declaring a FrameLayout around the two pages and setting the visibility of the second page to gone.
The switchToChild method (which I should probably rename to switch to page and actually pass in the 1 or 2 value) simply sets the appropriate views to visible during the animations:
private void switchToChild(final int child) {
final Interpolator accelerator = new AccelerateInterpolator();
final Interpolator decelerator = new DecelerateInterpolator();
final View visibleView;
final View invisibleView;
switch (child) {
case 0:
visibleView = findViewById(R.id.page_2);
invisibleView = findViewById(R.id.page_1);
findViewById(R.id.next).setVisibility(View.VISIBLE);
findViewById(R.id.back).setVisibility(View.GONE);
break;
case 1:
default:
visibleView = findViewById(R.id.page_1);
invisibleView = findViewById(R.id.page_2);
findViewById(R.id.back).setVisibility(View.VISIBLE);
findViewById(R.id.next).setVisibility(View.GONE);
break;
}
if (invisibleView.getVisibility() != View.VISIBLE) {
final ObjectAnimator visToInvis = ObjectAnimator.ofFloat(visibleView, "rotationY", 0f, 90f).setDuration(250);
visToInvis.setInterpolator(accelerator);
final ObjectAnimator invisToVis = ObjectAnimator.ofFloat(invisibleView, "rotationY", -90f, 0f).setDuration(250);
invisToVis.setInterpolator(decelerator);
visToInvis.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator anim) {
visibleView.setVisibility(View.GONE);
invisibleView.setVisibility(View.VISIBLE);
invisToVis.start();
}
});
visToInvis.start();
}
}
I've run into what I can only categorize as a memory leak for ScrollView elements when using the Gallery component.
A short background. I've got an existing app that is a photo slideshow app.
It uses the Gallery component, but each element in the adapter is displayed in full-screen.
(full source is available at this link)
The adapter View element consist of an ImageView, and two TextViews for title and description.
As the photos are of a quite high-resolution, the app uses quite a lot of memory but the Gallery has in general manage to recycle them well.
However, when I am now implementing a ScrollView for the description TextView, I almost immediately run into memory problems. This the only change I made
<ScrollView
android:id="#+id/description_scroller"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical"
android:fillViewport="true">
<TextView
android:id="#+id/slideshow_description"
android:textSize="#dimen/description_font_size"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#color/white"
android:layout_below="#id/slideshow_title"
android:singleLine="false"
android:maxLines="4"/>
</ScrollView>
I did a heap dump and could clearly see that it was the Scrollview which was the root of the memory problems.
Here are two screenshots from the heap dump analysis. Note that the ScrollView retains a reference to mParent which includes the large photo I use
PS same problem occurs if I use the TextView's scrolling (android:scrollbars = "vertical" and .setMovementMethod(new ScrollingMovementMethod());
PSS Tried switching off persistent drawing cache, but no different dreaandroid:persistentDrawingCache="none"
Have you tried removing the scroll view whenever it's container view scrolls off the screen? I'm not sure if that works for you but its worth a shot? Alternatively, try calling setScrollContainer(false) on the scroll view when it leaves the screen. That seems to remove the view from the mScrollContainers set.
Also, this question, answered by Dianne Hackborn (android engineer), explicitly states not to use scrollable views inside of a Gallery. Maybe this issue is why?
Just add this -> android:isScrollContainer="false"
<ScrollView
android:id="#+id/description_scroller"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical"
android:fillViewport="true"
android:isScrollContainer="false">
There is some source why this is appear:
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.1_r1/android/view/View.java
the problem is:
setScrollContainer(boolean isScrollContainer)
by default:
boolean setScrollContainer = false;
but in some cases like this
if (!setScrollContainer && (viewFlagValues&SCROLLBARS_VERTICAL) != 0) {
setScrollContainer(true);
}
it can be true, and when it happends
/**
* Change whether this view is one of the set of scrollable containers in
* its window. This will be used to determine whether the window can
* resize or must pan when a soft input area is open -- scrollable
* containers allow the window to use resize mode since the container
* will appropriately shrink.
*/
public void setScrollContainer(boolean isScrollContainer) {
if (isScrollContainer) {
if (mAttachInfo != null && (mPrivateFlags&SCROLL_CONTAINER_ADDED) == 0) {
mAttachInfo.mScrollContainers.add(this);
mPrivateFlags |= SCROLL_CONTAINER_ADDED;
}
mPrivateFlags |= SCROLL_CONTAINER;
} else {
if ((mPrivateFlags&SCROLL_CONTAINER_ADDED) != 0) {
mAttachInfo.mScrollContainers.remove(this);
}
mPrivateFlags &= ~(SCROLL_CONTAINER|SCROLL_CONTAINER_ADDED);
}
}
mAttachInfo.mScrollContainers.add(this) - all view put into ArrayList this lead to leak of memory sometimes
Yes i noticed the problem, sorry for my previous comment, i've tried to empty the Drawables
by setting previous Drawable.setCallBack(null); but didnt work, btw i have nearly the same project, i use ViewFlipper instead of Gallery, so i can control every thing, and i just use 2 Views in it, and switch between them, and no memory leak, and why not you resize the Image before displaying it, so it will reduce memory usage (search SO for resizing Image before reading it)
Try moving "android:layout_below="#id/slideshow_title" in TextView to ScrollView.
Ended up with implementing a workaround that uses a TextSwitcher that is automatically changed to the remaining substring every x seconds.
Here is the relevant xml definition from the layout
<TextSwitcher
android:id="#+id/slideshow_description"
android:textSize="#dimen/description_font_size"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="#+id/slideshow_description_anim1"
android:textSize="#dimen/description_font_size"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="2"
android:textColor="#color/white"
android:singleLine="false"/>
<TextView
android:id="#+id/slideshow_description_anim2"
android:textSize="#dimen/description_font_size"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="2"
android:textColor="#color/white"
android:singleLine="false"/>
</TextSwitcher>
Here I add the transition animation to the TextSwitcher (in the adapter's getView method)
final TextSwitcher slideshowDescription = (TextSwitcher)slideshowView.findViewById(R.id.slideshow_description);
Animation outAnim = AnimationUtils.loadAnimation(context,
R.anim.slide_out_down);
Animation inAnim = AnimationUtils.loadAnimation(context,
R.anim.slide_in_up);
slideshowDescription.setInAnimation(inAnim);
slideshowDescription.setOutAnimation(outAnim);
Here is how I swap to the part of the description
private void updateScrollingDescription(SlideshowPhoto currentSlideshowPhoto, TextSwitcher switcherDescription){
String description = currentSlideshowPhoto.getDescription();
TextView descriptionView = ((TextView)switcherDescription.getCurrentView());
//note currentDescription may contain more text that is shown (but is always a substring
String currentDescription = descriptionView.getText().toString();
if(currentDescription == null || description==null){
return;
}
int indexEndCurrentDescription= descriptionView.getLayout().getLineEnd(1);
//if we are not displaying all characters, let swap to the not displayed substring
if(indexEndCurrentDescription>0 && indexEndCurrentDescription<currentDescription.length()){
String newDescription = currentDescription.substring(indexEndCurrentDescription);
switcherDescription.setText(newDescription);
}else if(indexEndCurrentDescription>=currentDescription.length() && indexEndCurrentDescription<description.length()){
//if we are displaying the last of the text, but the text has multiple sections. Display the first one again
switcherDescription.setText(description);
}else {
//do nothing (ie. leave the text)
}
}
And finally, here is where I setup the Timer which causes it to update every 3.5 seconds
public void setUpScrollingOfDescription(){
final CustomGallery gallery = (CustomGallery) findViewById(R.id.gallery);
//use the same timer. Cancel if running
if(timerDescriptionScrolling!=null){
timerDescriptionScrolling.cancel();
}
timerDescriptionScrolling = new Timer("TextScrolling");
final Activity activity = this;
long msBetweenSwaps=3500;
//schedule this to
timerDescriptionScrolling.scheduleAtFixedRate(
new TimerTask() {
int i=0;
public void run() {
activity.runOnUiThread(new Runnable() {
public void run() {
SlideshowPhoto currentSlideshowPhoto = (SlideshowPhoto)imageAdapter.getItem(gallery.getSelectedItemPosition());
View currentRootView = gallery.getSelectedView();
TextSwitcher switcherDescription = (TextSwitcher)currentRootView.findViewById(R.id.slideshow_description);
updateScrollingDescription(currentSlideshowPhoto,switcherDescription);
//this is the max times we will swap (to make sure we don't create an infinite timer by mistake
if(i>30){
timerDescriptionScrolling.cancel();
}
i++;
}
});
}
}, msBetweenSwaps, msBetweenSwaps);
}
Finally I can put this problem to a rest :)
I have a custom Adapter that renders some items in a ListView. I need to show an icon on the ListView's items, if the item's text is ellipsized, and hide it if there's enough room for the text to finish. I have access to the button in getView method of my adapter (where I set the text) but the ellipses are not added immediately upon setting the text.
Is there any way I can do this?
Here's my TextView markup:
<TextView android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:singleLine="true"
android:id="#+id/list_item_description"/>
public int getEllipsisCount (int line):
Returns the number of characters to be ellipsized away, or 0 if no ellipsis is to take place.
So, simply call :
if(textview1.getLayout().getEllipsisCount() > 0) {
// Do anything here..
}
Since the getLayout() cant be called before the layout is set, use ViewTreeObserver to find when the textview is loaded:
ViewTreeObserver vto = textview.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
Layout l = textview.getLayout();
if ( l != null){
int lines = l.getLineCount();
if ( lines > 0)
if ( l.getEllipsisCount(lines-1) > 0)
Log.d(TAG, "Text is ellipsized");
}
}
});
And finally do not forget to remove removeOnGlobalLayoutListener when you need it nomore.
The tricky part is that the view you're working with in getView will sometimes have been laid out, and sometimes not, so you have to handle both cases.
When it hasn't been laid out, you set a view tree observer to check on the ellipsis once it has been. In the case of recycled views, the layout will already be there and you can check for the ellipsis immediately after setting the text.
public View getView(int position, View convertView, ViewGroup parent) {
final ViewHolder vh;
if (convertView == null) {
vh = new ViewHolder();
... // create/inflate view and populate the ViewHolder
}
vh = (ViewHolder) convertView.getTag();
// Set the actual content of the TextView
vh.textView.setText(...);
// Hide the (potentially recycled) expand button until ellipsizing checked
vh.expandBtn.setVisibility(GONE);
Layout layout = vh.textView.getLayout();
if (layout != null) {
// The TextView has already been laid out
// We can check whether it's ellipsized immediately
if (layout.getEllipsisCount(layout.getLineCount()-1) > 0) {
// Text is ellipsized in re-used view, show 'Expand' button
vh.expandBtn.setVisibility(VISIBLE);
}
} else {
// The TextView hasn't been laid out, so we need to set an observer
// The observer fires once layout's done, when we can check the ellipsizing
ViewTreeObserver vto = vh.textView.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
Layout layout = vh.textView.getLayout();
if (layout.getEllipsisCount(layout.getLineCount()-1) > 0) {
// Text is ellipsized in newly created view, show 'Expand' button
vh.expandBtn.setVisibility(VISIBLE);
}
// Remove the now unnecessary observer
// It wouldn't fire again for reused views anyways
ViewTreeObserver obs = vh.textView.getViewTreeObserver();
obs.removeGlobalOnLayoutListener(this);
}
});
return convertView;
}
I hope I understand your question correctly--if you're looking to end a TextView that's too wide for a screen with ellipses, you can add these attributes to your TextView:
android:ellipsize="end"
android:maxLines="1"
android:scrollHorizontally="true"
If, however, you want to determine whether a TextView is ended with an ellipsis or is displayed fully, I'm not so sure that's possible--it doesn't look like it is. Still, you might want to try the getEllipsize() method of TextView. I'm not sure whether that returns the point at where the TextView is ellipsized by Android, or where you have set the TextView to be ellipsized.
You can either set your text to marque.
add this in your code might help.....
android:ellipsize="marquee"
android:focusable="true"
android:focusableInTouchMode="true"
android:scrollHorizontally="true"
android:freezesText="true"
android:marqueeRepeatLimit="marquee_forever"
You can also put a horizontal scrollview for you code....
<HorizontalScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:id="#+id/list_item_description"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:ellipsize="end"
android:gravity="center_vertical"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceSmall" />
</LinearLayout>
</HorizontalScrollView>