Android Design: onDraw() not called after invalidate() - java

After I call the invalidate() method of my custom view (extends View) the onDraw() method is not called. This is caused by my app design but I not sure how to solve this problem.
I have two Layouts inside a FrameLayout (see below), which should work like layer in photo-editing software. In the code I add a different custom views to layout1 and layout2. After adding the second custom view to the second layout, the onDraw() method of this first custom view is not called after calling invalidate().
<FrameLayout
android:id="#+id/main_frameLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="#+id/main_layout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" />
<LinearLayout
android:id="#+id/main_layout2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" />
</FrameLayout>
In both custom views I draw a bitmap over the hole screen size and draw some lines. The drawing is implemented in both custom views like:
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (w > 0 && h > 0) {
this.bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
}
}
#Override
public void onDraw(Canvas canvas) {
canvas.drawBitmap(this.bitmap, 0, 0, new Paint());
drawSomeLines(canvas);
}
It all works fine if I only add one custom view to one layout. Has anybody an idea what is wrong with my structure/code?

I figured it out by myself, maybe the solution will help anybody.
The problem was that I added the custom views to different layouts. I removed the two LinearLayouts and added my custom views directly to the FrameLayout and it works fine.

Related

How can I anchor an image to the center within an Imageview in a scrollview, in Android?

When I scroll within my Android scrollview, it scrolls down as it should.
But I'd like the image within the imageview to anchor to the centre. So you always see the face of the person in the image as you scrolling up.
So far I have not been able to accomplish this
A similar effect is created in the image below:
Stockguy image:
My code so far (which so far doesn't accomplish this):
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fillViewport="true"
android:scrollbars="none"
android:background="#FAFAFA"
android:id="#+id/cScrollview"
>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="1100dp"
tools:context=".MainActivity"
android:id="#+id/CRLayout">
<ImageView
android:layout_gravity="center"
android:adjustViewBounds="true"
android:layout_width="601dp"
android:layout_height="250dp"
android:paddingTop="0dp"
android:paddingLeft="0dp"
android:paddingRight="0dp"
android:scaleType="centerCrop"
android:id="#+id/contactPic"
android:src="#drawable/stockguy"/>
....
</RelativeLayout>
</ScrollView>
To achieve parallax effect like on image you posted try following code. It is a very simple way.
Layout:
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:id="#+id/rlWrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/ivContactPhoto"
android:layout_width="match_parent"
android:layout_height="#dimen/contact_photo_height"
android:scaleType="centerCrop"
android:src="#drawable/stockguy" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="#dimen/contact_photo_height"
android:layout_marginTop="250dp">
<!-- Other Views -->
</LinearLayout>
</RelativeLayout>
</ScrollView>
LinearLayout's top margin is equal to the ImageViews's height.
Listening scroll position of the ScrollView and changing position of ImageView:
#Override
protected void onCreate(Bundle savedInstanceState) {
<...>
mScrollView = (ScrollView) findViewById(R.id.scrollView);
mPhotoIV = (ImageView) findViewById(R.id.ivContactPhoto);
mWrapperRL = (RelativeLayout) findViewById(R.id.rlWrapper);
mScrollView.getViewTreeObserver().addOnScrollChangedListener(new ScrollPositionObserver());
<...>
}
private class ScrollPositionObserver implements ViewTreeObserver.OnScrollChangedListener {
private int mImageViewHeight;
public ScrollPositionObserver() {
mImageViewHeight = getResources().getDimensionPixelSize(R.dimen.contact_photo_height);
}
#Override
public void onScrollChanged() {
int scrollY = Math.min(Math.max(mScrollView.getScrollY(), 0), mImageViewHeight);
// changing position of ImageView
mPhotoIV.setTranslationY(scrollY / 2);
// alpha you can set to ActionBar background
float alpha = scrollY / (float) mImageViewHeight;
}
}
Hope it will help.
I'd take a different approach with that.
Try building your layout based on the following snippet:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="#+id/image"
android:layout_width="match_parent"
android:background="#00FF00"
android:scaleType="centerCrop"
android:layout_height="200dp"/>
<FrameLayout
android:id="#+id/content"
android:layout_below="#+id/image"
android:background="#FF0000"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</RelativeLayout>
Using the GestureDetector.SimpleOnGestureListeneryou can detect when the user scrolls on the content layout and update the image size and alpha accordingly to get the effect you describe.
I would implement this using a scroll callback on the ScrollView, this doesn't exist by default but is easy to create yourself by extending ScrollView:
public class UpdatingScrollView extends ScrollView {
private OnScrollChangedListener mScrollChangedListener;
public interface OnScrollChangedListener {
public void onScrollChanged(int x, int y, int oldx, int oldy);
}
public UpdatingScrollView(Context context) {
super(context);
}
public UpdatingScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public UpdatingScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void setOnScrollChangedListener(OnScrollChangedListener listener) {
mScrollChangedListener = listener;
}
#Override
protected void onScrollChanged(int x, int y, int oldx, int oldy) {
super.onScrollChanged(x, y, oldx, oldy);
if (mScrollChangedListener != null) mScrollChangedListener.onScrollChanged(x, y, oldx, oldy);
}
}
So replace ScrollView in your layout with com.your.package.UpdatingScrollView.
In your Fragment/Activity where the UpdatingScrollView is defined you set the scroll listener and update the bottom margin of the image based on the scroll offset. For every 2 pixels the ScrollView has scrolled you'll want to update the bottom margin by -1 pixel which will make the image move up the screen at half the rate of the rest of the content.
So something like this:
scrollView.setOnScrollChangedListener(new OnScrollChangedListener() {
#Override
public void onScrollChanged(int x, int y, int oldx, int oldy) {
// I think y will be negative when scrolled
if(y >= -image.getHeight()) {
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) image.getLayoutParams();
lp.setMargins(0, 0, 0, y / 2);
image.setLayoutParams(lp);
}
}
});

Google maps v2 fragment view flickers when changing it's width

My app contains Google maps v2 API fragment view. On button click I want to change it's width from left to right side like this: Google map taking half screen --> Google map taking full screen.
It works, But I see strange black area on the right side of the screen during width transition.
I have read that it's Google bug and that they released fix for this but it only works since Android 4.1 version. I am using Android 4.0
Also I checked some workarounds mentioned by another people but all of them focuses on tasks such like scrolling, etc. Nobody is mentioning google maps view width change.
Anybody can help me?
My xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:map="http://schemas.android.com/apk/res-auto"
android:id="#+id/mainLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<fragment
android:id="#+id/map"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="510dp"
android:layout_height="match_parent"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp" />
<ImageView
android:id="#+id/mapExpandButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_marginBottom="25dp"
android:layout_marginRight="510dp"
android:background="#drawable/map_expand" />
<ImageView
android:id="#+id/mapCloseButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="25dp"
android:layout_marginLeft="15dp"
android:background="#drawable/map_close" />
Google maps init:
mapFragment = (SupportMapFragment)getActivity().getSupportFragmentManager().findFragmentById(R.id.map);
map = mapFragment.getMap ();
map.getUiSettings().setRotateGesturesEnabled(false); // disable rotation
map.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(54.6873439, 25.2770559) , 16.0f)); // set initial position
map.getUiSettings().setZoomControlsEnabled(false);
On "expand" button click, I am expanding google map like this:
ResizeWidthAnimation anim = new ResizeWidthAnimation(mapFragment.getView (), width);
anim.setDuration(400);
mapFragment.getView ().startAnimation(anim);
Animation class:
public class ResizeWidthAnimation extends Animation
{
private int mWidth;
private int mStartWidth;
private View mView;
public ResizeWidthAnimation(View view, int width)
{
mView = view;
mWidth = width;
mStartWidth = view.getWidth();
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t)
{
int newWidth = mStartWidth + (int) ((mWidth - mStartWidth) * interpolatedTime);
mView.getLayoutParams().width = newWidth;
mView.requestLayout();
}
#Override
public void initialize(int width, int height, int parentWidth, int parentHeight)
{
super.initialize(width, height, parentWidth, parentHeight);
}
#Override
public boolean willChangeBounds()
{
return true;
}
}
We solved this issue by adding a view on top of the map and animating this view width change. Nothing else worked.
Tips to avoid Google map flickering bug:
Don't do "add" or "replace" operations with the fragment which contains Google map fragment inside. Instead, use show/hide operations with fragment view. This will not recreate already created fragment when you use these operations. For example:
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction ft1 = manager.beginTransaction();
ft1.show (someFragment);
ft1.hide (someFragment2);
ft1.commit();
During app execution, don't try to change google map fragment dimension parameters (width, height, margin, etc.) at all. Search workarounds by using another (additional) views if you need some google map animations, width changes, etc.
Relayouting on each frame of your animation is very expensive (even more for a map) and you should not do that. If you really want to animate the bounds of your map, try to apply a scale animation, then at the end of it, relayout your View to its final new dimensions and disable scaling.
To improve animation performance using a map or any other SurfaceView, you can call requestTransparentRegion() on the MapFragment container, passing itself as argument.

split up a linear layout background into different colored parts

For a ListView, I want to give the individual ListView elements some kind of progress indicator - so to speak, I need a list of progress bars.
However, each of the listview elements have some text overlay in the form of a TextView that should not be affected by the progress bar at all.
I think pictures can tell more than words in this case, so here is pretty much what I want:
I know I can add "sublayouts" to the individual LinearLayouts and change the weight programmatically, which looks somewhat like this:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<LinearLayout
android:id="#leftSide"
android:background="#color/green"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="0.8" >
</LinearLayout>
<LinearLayout
android:id="#rightSide"
android:background="#color/red"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="0.2" >
</LinearLayout>
</LinearLayout>
and then programmatically change the weight for both sides (completed and uncompleted):
float weightLeft = 0.8f;
float weightRight = 1f-weightLeft;
android.widget.LinearLayout.LayoutParams paramsLeft = new android.widget.LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT, weightLeft);
LinearLayout left = (LinearLayout) findViewById(R.id.leftside);
left.setLayoutParams(paramsLeft);
android.widget.LinearLayout.LayoutParams paramsRight = new android.widget.LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT, weightRight);
LinearLayout right = (LinearLayout) findViewById(R.id.rightside);
left.setLayoutParams(paramsRight);
But - the question now is, how do I make the textviews sit on the parent linear layout and ignore the children LinearLayouts ?
I can also imagine there is a way to just split up the distribution of the background parts using drawables, but I have no clue how to do that, especially not programmatically.
Any help is appreciated!
try this custom Drawable, mFraction is [0..1]:
public class FractionDrawable extends Drawable {
private Paint mPaint;
private float mFraction;
public FractionDrawable(float fraction) {
mPaint = new Paint();
setFraction(fraction);
}
public void setFraction(float fraction) {
mFraction = fraction;
invalidateSelf();
}
#Override
public void draw(Canvas canvas) {
Rect b = getBounds();
mPaint.setColor(0xff00aa00);
float x = b.width() * mFraction;
canvas.drawRect(0, 0, x, b.height(), mPaint);
mPaint.setColor(0xffaa0000);
canvas.drawRect(x, 0, b.width(), b.height(), mPaint);
}
#Override
public void setAlpha(int alpha) {
}
#Override
public void setColorFilter(ColorFilter cf) {
}
#Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}

Custom component layout order

I have a custom component which is overriding the onLayout method as follows:
#Override
public void onLayout(boolean changed, int left, int top, int right, int bottom) {
int[] location = new int[2];
this.getLocationOnScreen(location);
int x = location[0];
int y = location[1];
int w = this.getWidth();
int h = y+(8*CELL_HEIGHT);
updateMonthName();
initCells();
CELL_MARGIN_LEFT = 0;
CELL_MARGIN_TOP = monthNameBounds.height();
CELL_WIDTH = w / 7;
setFrame(x, y, x+w, h);
super.onLayout(true, x, y, w, h);
}
However, when using this component in a linearlayout as follows:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:padding="10dp"
android:orientation="vertical">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="TextView above"
android:textSize="15sp"
/>
<com.project.calenderview.CalendarView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="TextView below"
android:textSize="15sp"
/>
</LinearLayout>
The layout is rendered such that the textview "TextView above" is first, "TextView below" is second and then my component which is expected to be in the middle between both.
Edit:
Here's also the onDraw:
#Override
protected void onDraw(Canvas canvas) {
// draw background
super.onDraw(canvas);
//Draw month name
canvas.drawText(monthName, (this.getWidth() / 2) - (monthNameBounds.width() / 2), monthNameBounds.height() + 10, monthNamePaint);
//Draw arrows
monthLeft.draw(canvas);
monthRight.draw(canvas);
// draw cells
for(Cell[] week : mCells) {
for(Cell day : week) {
if(day == null) continue;
day.draw(canvas);
}
}
}
What is it that am doing wrong here?
Thanks
the onLayout methods is passing int left, int top, int right, int bottom indicating where your view is supposed to draw, but apparently you're completely ignoring it. You muse use those values to layout it properly.
edit:
if you're not extending a ViewGroup you shouldn't be overriding onLayout(bool, int, int, int, int)
from the docs View.onLayout:
Called from layout when this view should assign a size and position to
each of its children. Derived classes with children should override
this method and call layout on each of their children.
Derived classes with children should override this method and call layout on each of their children
The issue you're having on the final result is in some other part of your code.

My background image is being drawn too big for the canvas

The background I'm trying to draw for my app seems to be getting scaled too large for some reason. I made sure the emulator is WVGA800, have it set up in the manifest and layout to be full screen and landscape (just like the image which is 800 x 480). I just don't see where it would scale the image.
Here's a picture of the problem. The image when put in the emulator, and then the actual image.
Here's some relavant code:
/* mBackground instantiated in the class constructor */
mBackground = BitmapFactory.decodeResource( mContext.getResources(), R.drawable.background );
private void doDraw( Canvas canvas )
{
canvas.drawBitmap( mBackground, 0, 0, null );
}
Here's the layout
<?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:orientation="horizontal">
<com.project.game.GameView
android:id="#+id/game"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
</RelativeLayout>
</FrameLayout>
one possibility is to create a canvas with a viewport
public void onDraw(Canvas canvas) {
Bitmap myImg = BitmapFactory.decodeResource(getResources(),
R.drawable.cal_blank);
canvas.setViewport(800, 480);
canvas.drawBitmap(iconImg, 0, 0, new Paint(););
}
another way is to scale your image with this method http://www.anddev.org/resize_and_rotate_image_-_example-t621.html
Just a guess - maybe it depends on what folder type is used to put the image in. Could you say where did you put the image? I mean, is it the /res/drawable-hdpi/ or anything else? Have you tried /res/drawable-nodpi/?

Categories