Android WebView flicker bug while animating view on device - android

I am attempting to implement slide-in navigation (a la Facebook, Path, or Google+ Android apps). The complete source to reproduce this bug can be found here.
When animating a WebView off the screen however, on a device, flickering occurs, as if the device is taking a bite out of my WebView just before pushing it off the screen.
The artifact does not occur on the emulator! The animation proceeds smoothly.
Interestingly, this flickering seems to only occur when the view being animated is a WebView! Here is the same project using a TextView as the main content view.
Layout for the web view activity:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/blue" >
<WebView
android:id="#+id/web_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true" />
</RelativeLayout>
And for the text view activity:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/blue" >
<TextView
android:id="#+id/web_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello there. no flicker!"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
/>
</RelativeLayout>
Code called to slide the navigation menu in:
private void slideIn() {
LinearLayout actionBarFrame = (LinearLayout) findViewById(android.R.id.content).getParent();
LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
menuView = inflater.inflate(R.layout.menu, null);
menuView.setLayoutParams(new FrameLayout.LayoutParams(SIZE, LayoutParams.MATCH_PARENT, Gravity.LEFT));
FrameLayout decorView = (FrameLayout) getWindow().getDecorView();
decorView.addView(menuView);
decorView.bringChildToFront(actionBarFrame);
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) actionBarFrame.getLayoutParams();
params.setMargins(SIZE, 0, -SIZE, 0);
actionBarFrame.setLayoutParams(params);
TranslateAnimation ta = new TranslateAnimation(-SIZE, 0, 0, 0);
ta.setDuration(DURATION);
actionBarFrame.startAnimation(ta);
}
And to slide it back out:
private void slideOut() {
LinearLayout actionBarFrame = (LinearLayout) findViewById(android.R.id.content).getParent();
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) actionBarFrame.getLayoutParams();
params.setMargins(0, 0, 0, 0);
actionBarFrame.setLayoutParams(params);
TranslateAnimation ta = new TranslateAnimation(SIZE, 0, 0, 0);
ta.setDuration(DURATION);
ta.setAnimationListener(new AnimationListener() {
#Override
public void onAnimationEnd(Animation arg0) {
((FrameLayout) getWindow().getDecorView()).removeView(menuView);
menuView = null;
}
#Override public void onAnimationRepeat(Animation arg0) { }
#Override public void onAnimationStart(Animation arg0) { }
});
actionBarFrame.startAnimation(ta);
}
I uploaded the code to BitBucket as a public repo so you can try this project exactly as I have.
Any help or ideas about why this is happening or how to fix it would be greatly appreciated! What is desired is to smoothly animate the WebView out of the picture and back in. Thanks!

Generally speaking, it seems the problem has to do with a bug when using WebViews with hardware acceleration enabled on 3.0+ devices. I also tried using the sliding menu library with a WebView but experienced the same flicker problem. After looking around I found a workaround here:
WebView “flashing” with white background if hardware acceleration is enabled (Android 3.0+)
The workaround uses the following code to set the layer type of the WebView to software rendering:
webview.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
This code solved the flicker for me with the drawback of lower performance.

Related

Android scaling popup window

i am writing a simple 2D Android Game (sort of puzzles). A user is drawing lines during the game and that is why I decided to an approach in which i resize in runtime to scale correctly on differrent devices. I prepared graphics for 1280x720 and I simply resize them in runtime inside:
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
FrameLayout container = (FrameLayout) findViewById(R.id.main);
LinearLayout mainView = (LinearLayout) findViewById(R.id.main_main_view);
TransformationOld transform = new TransformationOld();
transform.scaleViewToFitInContainerIsotropically(container, mainView);
}
}
The inspiration comes from: http://www.vanteon.com/downloads/Scaling_Android_Apps_White_Paper.pdf
It works fine for Activities, but I cannot have it working for popups.
The way I create popups:
public void onClickButtonMainAbout(View view) {
LayoutInflater inflater = (LayoutInflater) MainActivity.this
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View popupView = inflater.inflate(R.layout.popup_about_backup_with_pixels, null, false);
final PopupWindow pw = new PopupWindow(popupView, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
pw.showAtLocation(popupView, Gravity.CENTER, 0, 0);
Button button_ok = (Button) popupView.findViewById(R.id.about_button_ok);
button_ok.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
pw.dismiss();
}
});
}
The popup in xml:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/popup_about2"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="#+id/popup_about_main_view2">
<TableRow
android:id="#+id/tableRow1"
...
...
Any hints how to have such scaling working for popups to? Now it is simply displayed in 1280x720. I would be thankful for any advice.

Updating view position after animation

I'm working on getting a animation to work. I'm moving a search bar from the middle of the screen to the top of the screen. The animation works fine, but once it's moved I can't interact with anything. I've updated the position but can't interact with it regardless. Here's what I've got so far:
private void moveSearchToTop()
{
FrameLayout root = (FrameLayout) findViewById( R.id.rootLayout );
DisplayMetrics dm = new DisplayMetrics();
this.getWindowManager().getDefaultDisplay().getMetrics( dm );
statusBarOffset = dm.heightPixels - root.getMeasuredHeight();
int originalPos[] = new int[2];
mSearchBar.getLocationOnScreen( originalPos );
search_top = statusBarOffset - originalPos[1];
TranslateAnimation anim = new TranslateAnimation( 0, 0, 0, search_top);
anim.setDuration(500);
anim.setFillAfter(true);
anim.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
mSearchBar.layout(0, search_top, 0, mSearchBar.getHeight() + search_top);
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
mSearchBar.startAnimation(anim);
}
and here's what the view looks like in xml:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:id="#+id/search_bar"
android:background="#android:drawable/dialog_holo_light_frame">
<ImageView
android:id="#+id/search_icon"
android:layout_width="wrap_content"
android:onClick="onSearchClick"
android:layout_height="25dp"
android:layout_gravity="center_vertical"
android:src="#drawable/search"/>
<EditText
android:id="#+id/search_field"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:singleLine="true"
android:hint="Search"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="25dp"
android:layout_gravity="center_vertical"
android:src="#drawable/camera"/>
</LinearLayout>
Do I need to update the position of all of the child elements too? Thanks for a push in the right position.
No, you won't need to modify the child views. They will go where the search bar goes.
It looks to me like you are using your search_top variable both as a delta (for the TranslateAnimation constructor) and as a position within its parent (in the call to layout). It's really a delta, so the call to layout is incorrect.
Is the parent of your searchBar the "root" FrameLayout? If so, then you just want to use 0 as the new y coordinate in the call to layout. Also, you are using 0 as the right coordinate, which means that it has no width after the animation. This call would position your search bar at the upper-left edge of its parent, and maintain its current width and height:
mSearchBar.layout(0, 0, mSearchBar.getWidth(), mSearchBar.getHeight());
I also would recommend that you do not use screen position. It's safer to just set the position of views within parent views. For example, if the search bar's parent really is root, then you could get the delta with:
search_top = - mSearchBar.getTop();

Android Shadow on bottom of RelativeLayout outside of bounds

I have a 9 patch image of a shadow that I want to add to the bottom of a RelativeLayout. The layout fills the screen, but slides up then the user taps a button. So, I would like to have the shadow image be below the RelativeLayout (pulled down with a negative bottom margin) so that when the layout sides up, the shadow is on the bottom edge of the layout, giving it a layered effect.
<ImageView
android:id="#+id/shadow"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginBottom="-10dp"
android:src="#drawable/shadow" />
I am sliding up the frame using:
ObjectAnimator mover = ObjectAnimator.ofFloat(mainView, "translationY", !historyShown ? -ph : 0);
mover.setDuration(300);
mover.start();
Strangely, when I add the image to the layout and it give it a negative margin, it just isn't shown, like it is cutting off anything outside the bounds of the layout.
Is there a way around this?
I can think of one way of doing it, but I'm sure there must be a cleaner way.
Make the layout in which the mainView resides a FrameLayout (or RelativeLayout), and include the ImageView in that, making it a sibling of mainView, but list it before mainView. Set it to be at the bottom using layout_gravity="bottom" (or layout_alignParentBottom="true" if using a RelativeLayout).
Now change the target in the ObjectAnimator to something above the mainView (so either its container View or the Activity/Fragment), and change the property to something like "scrollUp", then create a method named setScrollUp(float) in the target. In this method set the translation of the mainView and shadow using setTranslationY(). Offset the shadow by its height.
Works for me here in a simple app using solid colours:
XML:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#f00">
<View
android:id="#+id/shadow"
android:layout_width="match_parent"
android:layout_height="20px"
android:layout_gravity="bottom"
android:background="#00f"/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#0f0"
android:id="#+id/container"/>
</FrameLayout>
Activity code:
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
container = findViewById(R.id.container);
shadow = findViewById(R.id.shadow);
container.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(final View v)
{
final float translationTo = (open) ? 0 : -300;
final float translationFrom = (open) ? -300 : 0;
open = !open;
ObjectAnimator anim = ObjectAnimator.ofFloat(MyActivity.this, "scrollUp", translationFrom, translationTo);
anim.setDuration(500);
anim.start();
}
});
}
public void setScrollUp(final float position)
{
container.setTranslationY(position);
shadow.setTranslationY(position + shadow.getHeight());
}

View over Canvas Visibility issue

I have this layout:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<com.components.game.GameView
android:id="#+id/game_id"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
<RelativeLayout
android:id="#+id/ChatLayout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:visibility="invisible"
android:focusable="true"
android:focusableInTouchMode="true" >
<Button
android:id="#+id/ChatCancelButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="X" />
<Button
android:id="#+id/ChatOkButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text="OK" />
<EditText
android:id="#+id/ChatEditText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toLeftOf="#+id/ChatOkButton"
android:layout_toRightOf="#+id/ChatCancelButton"
android:maxLength="50"
android:singleLine="true" />
</RelativeLayout>
</RelativeLayout>
It's a RelativeLayout over a canvas. At start time it's invisible but when a user clicks a button the layout should become visible.
The problem is that it's not becoming visible. The layout is there but it's just not drawing it. If I press the position where the layout should appear it receives the event and opens the keyboard but it's not drawing the whole layout.
What is the problem?
If I set the RelativeLayout to visible at the beginning it works fine. it shows the layout and if I toggle between invisible and visible it works fine.
I made a workaround that almost always works.
I start the layout visible and than do that in the oncreate:
chatLayout.postDelayed(new Runnable() {
#Override
public void run() {
chatLayout.setVisibility(View.INVISIBLE);
}
}, 50);
But I don't like it and want to understand what's the problem.
The code:
It starts from a canvas button which send a message to a handler:
public void showInputLayout() {
Message.obtain(gameHandler, SHOW_INPUT_LAYOUT).sendToTarget();
}
In the handler:
case SHOW_INPUT_LAYOUT:
gameActivity.setChatVisibility(true);
break;
setChatVisibility:
public void setChatVisibility(boolean isVisible) {
int visible = isVisible ? View.VISIBLE : View.INVISIBLE;
chatLayout.setVisibility(visible);
if(isVisible){
chatEditText.setFocusable(true);
chatEditText.requestFocus();
}
}
Add a click listener to RelativeLayout and switch the visibility between GONE and VISIBLE. Try something like this:
int visibility = View.VISIBLE;
RelativeLayout layout = (RelativeLayout)findViewById(R.id.ChatLayout);
layout.setVisibility(visibility);
layout.setOnClickListener(new View.OnClickListener{
public void onClick(View v)
{
if(visibility == View.VISIBLE)
visibility = View.GONE;
else
visibility = View.VISIBLE;
v.setVisibility(visibility);
}
})
I ran into a similar issue recently, and for my case the problem was actually in the onDraw() method of the view underneath (should be com.components.game.GameView in your case). See if you can add calls to Canvas' getSaveCount(), save() and restoreToCount() in your drawing code, similar to this:
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int saveCount = canvas.getSaveCount();
canvas.save();
// custom drawing code here ...
// use Region.Op.INTERSECT for adding clipping regions
canvas.restoreToCount(saveCount);
}
I believe what happened was that sometimes the framework set the clipping regions for the elements on top of our Canvas-drawing widget before our onDraw() method is called so we need to make sure that those regions are preserved.
Hope this helps.

How to use the xml setting in a view of a activity?

I want to show two views in one activity. If I clicked on button in the first view I want to see the second and other way round.
The views should not have the same size as the screen so I want e.g. to center it, like you see in first.xml.
But if I add the views with
addContentView(mFirstView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
the views are not centered. They are shown at top left.
How can I use the xml settings to e.g. center it?
first.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_height="wrap_content" android:layout_width="wrap_content"
android:background="#drawable/background"
android:layout_gravity="center"
android:minWidth="100dp"
android:minHeight="100dp"
android:paddingBottom="5dp"
>
<LinearLayout android:id="#+id/head"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageButton android:id="#+id/first_button"
android:src="#drawable/show_second"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#null" />
</LinearLayout>
second.xml same as first.xml but with
<ImageButton android:id="#+id/second_button"
android:src="#drawable/show_first"
... />
ShowMe.java
public class ShowMe extends Activity {
View mFirstView = null;
View mSecondView = null;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initFirstLayout();
initSecondLayout();
showFirst();
}
private void initFirstLayout() {
LayoutInflater inflater = getLayoutInflater();
mFirstView = inflater.inflate(R.layout.first, null);
getWindow().addContentView(mFirstView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
ImageButton firstButton = (ImageButton)mMaxiView.findViewById(R.id.first_button);
firstButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
ShowMe.this.showSecond();
}
});
}
private void initSecondLayout() {
// like initMaxiLayout()
}
private void showFirst() {
mSecondView.setVisibility(View.INVISIBLE);
mFirstView.setVisibility(View.VISIBLE);
}
private void showSecond() {
mFirstView.setVisibility(View.INVISIBLE);
mSecondView.setVisibility(View.VISIBLE);
}}
Hope someone can help.
Thanks
Why don't you use setContentView(R.layout.yourlayout)? I believe the new LayoutParams you're passing in addContentView() are overriding those you defined in xml.
Moreover, ViewGroup.LayoutParams lacks the layout gravity setting, so you would have to use the right one for the layout you're going to add the view to (I suspect it's a FrameLayout, you can check with Hierarchy Viewer). This is also a general rule to follow. When using methods that take layout resources as arguments this is automatic (they might ask for the intended parent).
With this consideration in mind, you could set your layout params with:
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(/* wrap wrap */);
lp.setGravity(Gravity.CENTER);
addContentView(mYourView, lp);
But I would recommend setContentView() if you have no particular needs.
EDIT
I mean that you create a layout like:
~~~/res/layout/main.xml~~~
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="....."
android:id="#+id/mainLayout"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
then in your onCreate() or init...Layout():
setContentView(R.layout.main);
FrameLayout mainLayout = (FrameLayout)findViewById(R.id.mainLayout);
// this version of inflate() will automatically attach the view to the
// specified viewgroup.
mFirstView = inflater.inflate(R.layout.first, mainLayout, true);
this will keep the layout params from xml, because it knows what kind it needs. See reference.

Categories

Resources