How to add sticky header to parallax ScrollView? - android

I've created ParallaxScrollView based on this answer and parallax effect works great with the ImageView.
public class ParallaxScrollView extends ScrollView {
private Activity activity;
private View imgContainer;
private ImageView imageView;
private float parallaxValue;
public ParallaxScrollView(final Context context) {
this(context, null);
}
public ParallaxScrollView(final Context context, final AttributeSet attrs) {
this(context, attrs, 0);
}
public ParallaxScrollView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
}
public void setParallaxParams(final Activity activity, final View imgContainer, final ImageView imageView, final float parallaxValue) {
this.activity = activity;
this.imgContainer = imgContainer;
this.imageView = imageView;
this.parallaxValue = parallaxValue;
}
#Override
protected void onScrollChanged(final int l, final int t, final int oldl, final int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (activity == null || imgContainer == null || imageView == null) {
throw new IllegalStateException("Please set parallax parameters via setParallaxParams() method!");
}
if (imageView.getTop() == 0) {
imageView.setTranslationY(getScrollY() * parallaxValue);
return;
}
// Get scroll view screen bound
final Rect scrollBounds = new Rect();
getHitRect(scrollBounds);
// Check if image container is visible in the screen
// so to apply the translation only when the container is visible to the user
if (imgContainer.getLocalVisibleRect(scrollBounds)) {
final Display display = activity.getWindowManager().getDefaultDisplay();
final DisplayMetrics outMetrics = new DisplayMetrics ();
display.getMetrics(outMetrics);
// Get screen density
final float density = getResources().getDisplayMetrics().density;
// Get screen height in pixels
final float dpHeight = outMetrics.heightPixels / density;
final float screenHeightPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpHeight, getResources().getDisplayMetrics());
final float halfScreenHeight = screenHeightPixels / 2;
// Get image container height in pixels
final float containerHeightPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, imgContainer.getHeight(), getResources().getDisplayMetrics());
// Get the location that consider a vertical center for the image (where the translation should be zero)
final float center = halfScreenHeight - (containerHeightPixels / 2);
// get the location (x,y) of the image container in pixels
final int[] locScreen = {0,0};
imgContainer.getLocationOnScreen(locScreen);
// trying to transform the current image container location into percentage
// so when the image container is exaclty in the middle of the screen percentage should be zero
// and as the image container getting closer to the edges of the screen should increase to 100%
final float finalLoc = ((locScreen[1]-center) * 100)/ halfScreenHeight;
// translate the inner image taking consideration also the density of the screen
imageView.setTranslationY(-finalLoc * parallaxValue * density);
}
}
}
And here is how I use it:
parallaxScrollView.setParallaxParams(getActivity(), imageContainer, image, 0.5f);
Now, I would like to add a sticky header to the Toolbar, so when the user would scroll down and the header will be on top of the screen below Toolbar it will "stick" to the Toolbar and when the user scrolls up and the content below header reaches it's top it should "unstick". Hard to explain, but I hope it is clear :)
Here is how the layout looks like:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ParallaxScrollView
android:id="#+id/scroll_view"
android:fillViewport="true"
android:overScrollMode="never"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<FrameLayout
android:id="#+id/image_container"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<ImageView
android:id="#+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:scaleType="centerCrop" />
</FrameLayout>
<FrameLayout
android:id="#+id/sticky_header"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<!-- Content -->
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="#dimen/padding_big"
android:paddingBottom="#dimen/padding_big"
android:paddingEnd="#dimen/padding_big"
android:paddingStart="#dimen/padding_big"
android:paddingLeft="#dimen/padding_big"
android:paddingRight="#dimen/padding_big">
<!-- Content -->
So, here basically I want to "stick" FrameLayout with id #+id/stickyHeader to the top when user scrolls.
Very similar approach AirBnb uses in their Android application (take a look at sticky tab bar at this link).
Any ideas how to do that?
Ideally, without using third-party libraries.

Related

Imageview parallax as you scroll

I saw many examples demonstrative parallax background as you scroll or listview parallax but I cannot find a clear example how to implement a parallax effect on images as you scroll in the activity.
An example implementation can be found in Airbnb app. As you scroll down you can see more of the image's bottom and as you scroll up you can see more of the image's top.
Any hints and tips on how to create such effect?
There are a few libraries that to a parallax effect, it depends on your app if they are useful for your particular case, for example:
ParallaxScroll
Paralloid
Google is your friend pal ;) if none of these suits your needs then you have to create a custom ScrollView but that's a longer story, first give them a try and post your results.
Edit
If none of these fit your requeriments then this is what you have to do:
First, create a custom ScrollView so you can listen to scroll changes.
public class ObservableScrollView extends ScrollView {
public interface OnScrollChangedListener {
public void onScrollChanged(int deltaX, int deltaY);
}
private OnScrollChangedListener mOnScrollChangedListener;
public ObservableScrollView(Context context) {
super(context);
}
public ObservableScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ObservableScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if(mOnScrollChangedListener != null) {
mOnScrollChangedListener.onScrollChanged(l - oldl, t - oldt);
}
}
public void setOnScrollChangedListener(OnScrollChangedListener listener) {
mOnScrollChangedListener = listener;
}
}
Obviously you need to use this in your layout instead of the default ScrollView:
<your.app.package.ObservableScrollView
android:id="#+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
Also you need to wrap your ImageView inside a container to make the parallax work:
<FrameLayout
android:id="#+id/img_container"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<ImageView
android:id="#+id/img"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop" />
</FrameLayout>
Finally set your Activity as a listener for your brand new ObservableScrollView and let the parallax begin:
public class MyActivity extends Activity implements ObservableScrollView.OnScrollChangedListener {
private ObservableScrollView mScrollView;
private View imgContainer;
#Override
public void onCreate(Bundle savedInstanceState) {
// Init your layout and set your listener
mScrollView = (ObservableScrollView)findViewById(R.id.scroll_view);
mScrollView.setOnScrollChangedListener(this);
// Store the reference of your image container
imgContainer = findViewById(R.id.img_container);
}
#Override
public void onScrollChanged(int deltaX, int deltaY) {
int scrollY = mScrollView.getScrollY();
// Add parallax effect
imgContainer.setTranslationY(scrollY * 0.5f);
}
}
You can modify the 0.5 value depending on how much parallax you want.
Edit
The above answer works fine if your ImageView is in the top of the activity. I am posting some code below to add the functionality to have the ImageView anywhere in the activity layout which I successfully made to work. These are generic calculations (might have some mistakes) and with a little tweak you can have it working for your own case.
For this example I have a fix height for the image container 200dp and for the image 240dp. The main purpose is when the image container is in the middle of the screen have no parallax effect and as the user scroll up or down to apply the effect. So as the image container get closer to the top of the screen or closer to the bottom of the screen the more of the parallax effect will be applied. The following calculations are a little hard to understand by reading them so try to make an example with real numbers in paper.
public class MyActivity extends Activity implements ObservableScrollView.OnScrollChangedListener {
private ObservableScrollView mScrollView;
private View imgContainer;
private ImageView mImageView;
#Override
public void onCreate(Bundle savedInstanceState) {
// Init your layout and set your listener
mScrollView = (ObservableScrollView)findViewById(R.id.scroll_view);
mScrollView.setOnScrollChangedListener(this);
// Store the reference of your image container
imgContainer = findViewById(R.id.img_container);
// Store the reference of your image
mImageView = findViewById(R.id.img);
}
#Override
public void onScrollChanged(int deltaX, int deltaY) {
// Get scroll view screen bound
Rect scrollBounds = new Rect();
mScrollView.getHitRect(scrollBounds);
// Check if image container is visible in the screen
// so to apply the translation only when the container is visible to the user
if (imgContainer.getLocalVisibleRect(scrollBounds)) {
Display display = getWindowManager().getDefaultDisplay();
DisplayMetrics outMetrics = new DisplayMetrics ();
display.getMetrics(outMetrics);
// Get screen density
float density = getResources().getDisplayMetrics().density;
// Get screen height in pixels
float dpHeight = outMetrics.heightPixels / density;
int screen_height_pixels = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpHeight, getResources().getDisplayMetrics());
int half_screen_height = screen_height_pixels/2;
// Get image container height in pixels
int container_height_pixels = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200, getResources().getDisplayMetrics());
// Get the location that consider a vertical center for the image (where the translation should be zero)
int center = half_screen_height - (container_height_pixels/2);
// get the location (x,y) of the image container in pixels
int[] loc_screen = {0,0};
imgContainer.getLocationOnScreen(loc_screen);
// trying to transform the current image container location into percentage
// so when the image container is exaclty in the middle of the screen percentage should be zero
// and as the image container getting closer to the edges of the screen should increase to 100%
int final_loc = ((loc_screen[1]-center)*100)/half_screen_height;
// translate the inner image taking consideration also the density of the screen
mImageView.setTranslationY(-final_loc * 0.4f * density);
}
}
}
I hope it can help someone that is looking for similar functionality.
scrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
#Override
public void onScrollChanged() {
int top = scrollView.getScrollY(); // Increases when scrolling up ^
if(top != 0) {
int newTop = (int) (top * .5f);
imageFrame.setTop(newTop < 0 ? 0 : newTop);
}
}
});

Paralax effect in app background

Im new in Android world. I want to put some parallax background effects in my app.
How can I do it? How to approach to this in Android?
Is there any productive way to create 2-3 layer parallax background? Is there some tool, or class in android API?
Or maybe I have to modify background image location or margins "manually" in code?
Im using API level 19.
I have tried to understand Paralloid library, but this is too big to understand without any explanation. Im new to Android and Java, im not familiar with all Layouts and other UI objects, however I'm familiar with MVC.
I started bounty, maybe someone can explain step by step how that library works.
This is what you can do:
In your activity/fragment layout file specify 2 ScrollView's (say background_sv and content_sv).
<?xml version="1.0" encoding="utf-8"?>
<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="match_parent" >
<com.example.parallax.MyScrollView
android:id="#+id/background_sv"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:id="#+id/parallax_bg"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="..." />
</com.example.parallax.MyScrollView>
<com.example.parallax.MyScrollView
android:id="#+id/content_sv"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
</LinearLayout>
</com.example.parallax.MyScrollView>
</RelativeLayout>
Add a dummy view in the content scrollview of the height of the background and make it transparent. Now, attach a scroll listener to the content_sv. When the content scrollview is scrolled, call
mBgScrollView.scrollTo(0, (int)(y /*scroll Of content_sv*/ / 2f));
The existing API's doesn't have the support to get the scroll events.
Hence, we need to create a Custom ScrollView, to provide the ScrollViewListener.
package com.example.parallax;
// imports;
public class MyScrollView extends ScrollView {
public interface ScrollViewListener {
void onScrollChanged(MyScrollView scrollView, int x, int y, int oldx, int oldy);
}
private ScrollViewListener scrollViewListener = null;
public MyScrollView(Context context) {
super(context);
}
public MyScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public MyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setScrollViewListener(ScrollViewListener scrollViewListener) {
this.scrollViewListener = scrollViewListener;
}
#Override
protected void onScrollChanged(int x, int y, int oldx, int oldy) {
super.onScrollChanged(x, y, oldx, oldy);
if(scrollViewListener != null) {
scrollViewListener.onScrollChanged(this, x, y, oldx, oldy);
}
}
}
Here is the activity which hosts both the content ScrollView and background ScrollView
package com.example.parallax;
// imports;
public class ParallaxActivity extends Activity implements ScrollViewListener {
private MyScrollView mBgScrollView;
private MyScrollView mContentScrollView;
#Override
public void onCreate(Bundle savedInstanceState) {
mBgScrollView = findViewById(R.id.background_sv);
mContentScrollView = findViewById(R.id.content_sv);
mContentScrollView.setOnScrollListener(this);
}
// this is method for onScrollListener put values according to your need
#Override
public void onScrollChanged(MyScrollView scrollView, int x, int y, int oldx, int oldy) {
super.onScrollChanged(scrollView, x, y, oldx, oldy);
// when the content scrollview will scroll by say 100px,
// the background scrollview will scroll by 50px. It will
// look like a parallax effect where the background is
// scrolling with a different speed then the content scrollview.
mBgScrollView.scrollTo(0, (int)(y / 2f));
}
}
I think the question is unclear, so this is not really an answer so much as an attempt to clarify with more detail than I could include in a comment.
My question is about what kind of parallax effect you want to achieve. Given these three examples (they are demo apps you can install from the Play Store), which if any has the type of parallax effect you want? Please answer in a comment.
Paralloid Demo
Parallax Scroll Demo
Google IO App
Given an answer, we all will find it easier to help out. If you edit your question to include this information, it will be improved.
The following contains an example application published by the author of Paralloid:
https://github.com/chrisjenx/Paralloid/tree/master/paralloidexample
From the GitHub page under the 'Getting Started' section:
Layout
ScrollView
This is an example, please refer to the paralloidexample App for full
code.
<FrameLayout ..>
<FrameLayout
android:id="#+id/top_content"
android:layout_width="match_parent"
android:layout_height="192dp"/>
<uk.co.chrisjenx.paralloid.views.ParallaxScrollView
android:id="#+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<LinearLayout
android:id="#+id/scroll_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="192dp"/>
</uk.co.chrisjenx.paralloid.views.ParallaxScrollView>
</FrameLayout>
Fragment
Inside your onViewCreated() or onCreateView().
//...
FrameLayout topContent = (FrameLayout) rootView.findViewById(R.id.top_content);
ScrollView scrollView = (ScrollView) rootView.findViewById(R.id.scroll_view);
if (scrollView instanceof Parallaxor) {
((Parallaxor) scrollView).parallaxViewBy(topContent, 0.5f);
}
// TODO: add content to top/scroll content
Thats it!
Have a look at the Parallaxor interface for applicable Parallax
methods.
Hope this helps!
Also, here is a link to Google's 'getting started' page for android.
Also, here is a link to a 'java tutorial for complete beginners'.
As well as link to some documentation about layouts, which 'define the visual structure for a user interface'.
That being said, you would use the layout to define what the interface looks like and use the subsequent example code to define what happens when you interact with it.
P.S. You can see the application in action here
I use the ParallaxScroll library. Very easy to use, good samples and well documented.
Here is how it can be done using ScrollView and it's background image. I've committed the code in github.
You need to extend the ScrollView and Drawable classes.
By default the ScrollView background height will be same as viewport height. To achieve the parallax effect, the background height should be larger and should be based on the ScrollView child height and the background scrolling factor we want to impose.
Background scroll factor of 1 indicates, background height is same as ScrollView child height and hence background will scroll with same offset as the child scrolls.
0.5 indicates, background height is 0.5 times ScrollView child extended height and will scroll 50% slower compared to the child contents. This effectively brings the parallax scrolling effect.
Call following method from ScrollView constructor:
void init() {
// Calculate background drawable size before first draw of scrollview
getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
// Remove the listener
getViewTreeObserver().removeOnPreDrawListener(this);
mDrawable = (ParallaxDrawable) getBackground();
if(mDrawable != null && mDrawable instanceof ParallaxDrawable) {
// Get the only child of scrollview
View child = getChildAt(0);
int width = child.getWidth();
// calculate height of background based on child height and scroll factor
int height = (int) (getHeight() + (child.getHeight() - getHeight()) * mScrollFactor);
mDrawable.setSize(width, height);
}
return true;
}
});
}
When ScrollView is scrolled, take into consideration the scroll offset while drawing the background. This basically achieves the parallax effect.
ParallaxScrollView:
protected void onScrollChanged(int x, int y, int oldX, int oldY) {
if(mDrawable != null && mDrawable instanceof ParallaxDrawable) {
// set the scroll offset for the background drawable.
mDrawable.setScrollOffset(x*mScrollFactor, y*mScrollFactor);
}
}
ParallaxDrawable:
#Override
public void draw(Canvas canvas) {
// To move the background up, translate canvas by negative offset
canvas.translate(-mScrollXOffset, -mScrollYOffset);
mDrawable.draw(canvas);
canvas.translate(mScrollXOffset, mScrollYOffset);
}
protected void onBoundsChange(Rect bounds) {
// This sets the size of background drawable.
mDrawable.setBounds(new Rect(bounds.top, bounds.left, bounds.left + mWidth, bounds.top + mHeight));
}
Usage of ParallaxScrollView and ParallaxDrawable:
public class MyActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.parallax_layout);
final ParallaxScrollView scrollView = (ParallaxScrollView) findViewById(R.id.sv);
ParallaxDrawable drawable = new ParallaxDrawable(getResources().getDrawable(R.drawable.bg));
scrollView.setBackground( drawable, 0.2f );
}
}
parallax_layout.xml:
<manish.com.parallax.ParallaxScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/sv"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="#+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#fff"
android:text="#string/text" />
<View
android:layout_width="match_parent"
android:layout_height="5dp" />
...
</LinearLayout>
</manish.com.parallax.ParallaxScrollView>
The Android API does not support much concrete tools for it as you probably noticed. In API 20 they added elevation which is an attribute for depth. This does not support parallax layouts itself but I would say it's a step by Google to make this kind of work easier. If you want a wild guess on if and when, I would say that parallax utilities could be added before API 25 is released, based on the latest update and the progress in battery efficiency.
For now all you need is to listen for some kind of movement and change the views positions based on a value representing elevation.
Your question made me upgrade my own project and this is how I did it using ViewDragHelper inside a Fragment.
public class MainFragment extends Fragment implements View.OnTouchListener {
private ImageView mDecor, mBamboo, mBackgroundBamboo;
private RelativeLayout mRootLayout;
private ViewDragHelper mDragHelper;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mRootLayout = (RelativeLayout) inflater.inflate(R.layout.fragment_main, container, false);
mRootLayout.setOnTouchListener(this);
mDecor = (ImageView) mRootLayout.findViewById(R.id.decor);
mBamboo = (ImageView) mRootLayout.findViewById(R.id.bamboo);
mBackgroundBamboo = (ImageView) mRootLayout.findViewById(R.id.backround_bamboo);
mDragHelper = ViewDragHelper.create(mRootLayout, 1.0f, new ViewDragHelper.Callback() {
private final float MAX_LEFT = -0;
private final float MAX_TOP = -20;
private final float MAX_RIGHT = 50;
private final float MAX_BOTTOM = 10;
private final float MULTIPLIER = 0.1f;
private final int DECOR_ELEVATION = 3;
private final int FRONT_BAMBOO_ELEVATION = 6;
private final int BACKGROUND_BAMBOO_ELEVATION = 1;
private float mLeft = 0;
private float mTop = 0;
#Override
public boolean tryCaptureView(View view, int i) {
return true;
}
#Override
public int clampViewPositionVertical(View child, int top, int dy) {
mTop += dy * MULTIPLIER;
mTop = mTop > MAX_BOTTOM ? MAX_BOTTOM : mTop < MAX_TOP ? MAX_TOP : mTop;
mDecor.setTranslationY(mTop * DECOR_ELEVATION);
mBamboo.setTranslationY(mTop * FRONT_BAMBOO_ELEVATION);
mBackgroundBamboo.setTranslationY(mTop * BACKGROUND_BAMBOO_ELEVATION);
return 0;
}
#Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
mLeft += dx * MULTIPLIER;
mLeft = mLeft < MAX_LEFT ? MAX_LEFT : mLeft > MAX_RIGHT ? MAX_RIGHT : mLeft;
mDecor.setTranslationX(mLeft * DECOR_ELEVATION);
mBamboo.setTranslationX(mLeft * FRONT_BAMBOO_ELEVATION);
mBackgroundBamboo.setTranslationX(mLeft * BACKGROUND_BAMBOO_ELEVATION);
return 0;
}
#Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy){
mRootLayout.requestLayout();
}
});
return mRootLayout;
}
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
mDragHelper.processTouchEvent(motionEvent);
// you can still use this touch listener for buttons etc.
return true;
}
}
Hi You can go with the below-given code for ParallaxView class
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.util.ArrayList;
public class ParallaxView extends SurfaceView implements Runnable {
private volatile boolean running;
private Thread gameThread = null;
// For drawing
private Paint paint;
private Canvas canvas;
private SurfaceHolder ourHolder;
// Holds a reference to the Activity
Context context;
// Control the fps
long fps =60;
// Screen resolution
int screenWidth;
int screenHeight;
ParallaxView(Context context, int screenWidth, int screenHeight) {
super(context);
this.context = context;
this.screenWidth = screenWidth;
this.screenHeight = screenHeight;
// Initialize our drawing objects
ourHolder = getHolder();
paint = new Paint();
}
#Override
public void run() {
while (running) {
long startFrameTime = System.currentTimeMillis();
update();
draw();
// Calculate the fps this frame
long timeThisFrame = System.currentTimeMillis() - startFrameTime;
if (timeThisFrame >= 1) {
fps = 1000 / timeThisFrame;
}
}
}
private void update() {
// Update all the background positions
}
private void draw() {
if (ourHolder.getSurface().isValid()) {
//First we lock the area of memory we will be drawing to
canvas = ourHolder.lockCanvas();
//draw a background color
canvas.drawColor(Color.argb(255, 0, 3, 70));
// Draw the background parallax
// Draw the rest of the game
paint.setTextSize(60);
paint.setColor(Color.argb(255, 255, 255, 255));
canvas.drawText("I am a plane", 350, screenHeight / 100 * 5, paint);
paint.setTextSize(220);
canvas.drawText("I'm a train", 50, screenHeight / 100*80, paint);
// Draw the foreground parallax
// Unlock and draw the scene
ourHolder.unlockCanvasAndPost(canvas);
}
}
// Clean up our thread if the game is stopped
public void pause() {
running = false;
try {
gameThread.join();
} catch (InterruptedException e) {
// Error
}
}
// Make a new thread and start it
// Execution moves to our run method
public void resume() {
running = true;
gameThread = new Thread(this);
gameThread.start();
}
}// End of ParallaxView
To know more you can go **
here
**: http://gamecodeschool.com/android/coding-a-parallax-scrolling-background-for-android/

Align the child views in center of the ViewPager android

I need to set the child view as center of the ViewPager and also I would like to show some part of the next and previous views to the current view sides(like current screen below 1). But currently the current view is starting at left side of the ViewPager(like expected screen below 2). How can I achieve that?
Here is my code..
MyViewPagerAdapter
public class MyViewPagerAdapter extends PagerAdapter {
private Activity mActivity;
private int mPageCount;
public MyViewPagerAdapter(Activity activity,int pageCount) {
mActivity = activity;
mPageCount = pageCount;
}
#Override
public int getCount() {
return mPageCount;
}
#Override
public boolean isViewFromObject(View view, Object obj) {
return (view ==(View)obj);
}
#Override
public Object instantiateItem(ViewGroup container,final int position) {
ViewGroup viewGroup = (ViewGroup)mActivity.getLayoutInflater().inflate(
R.layout.item_view, null);
viewGroup.setBackgroundColor(randomColor());
TextView textView = (TextView)viewGroup.findViewById(R.id.textView1);
textView.setText("Page: "+(position+1));
Button button = (Button) viewGroup.findViewById(R.id.button1);
button.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(mActivity, "Hey, Its clicked!!! at page "+(position+1), Toast.LENGTH_LONG).show();
}
});
container.addView(viewGroup);
return viewGroup;
}
Random rnd = new Random();
private int randomColor(){
return Color.argb(255, rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256));
}
#Override
public void destroyItem(ViewGroup collection, int position, Object view) {
//must be overridden else throws exception as not overridden.
Log.d("Tag", collection.getChildCount()+"");
collection.removeView((View) view);
}
#Override
public float getPageWidth(int position) {
return 0.8f;
}
}
MainActivity
public class MainActivity extends Activity {
private ViewPager viewPager;
LinearLayout linearLayout;
private int ID = 100;
private final int count = 8;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
viewPager = (ViewPager) findViewById(R.id.viewPager);
linearLayout = (LinearLayout) findViewById(R.id.indicator_layout);
generateIndicators(count);
MyViewPagerAdapter adapter = new MyViewPagerAdapter(this, count);
viewPager.setAdapter(adapter);
viewPager.setOnPageChangeListener(new OnPageChangeListener() {
int oldPosition = 0;
#Override
public void onPageSelected(int position) {
//this changes the old position's view state image
((TextView)linearLayout.getChildAt(oldPosition)).setText("");
oldPosition = position;
//this changes the current position's view state image
((TextView)linearLayout.getChildAt(position)).setText((position+1)+"");
}
//this method will be called repeatedly upto another item comes as front one(active one)
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}
//this will be called as per scroll state
#Override
public void onPageScrollStateChanged(int arg0) {
}
});
viewPager.setOffscreenPageLimit(4);
}
private void generateIndicators(int count) {
/// Converts 14 dip into its equivalent px
int padd = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3, getResources().getDisplayMetrics());
for(int i=0;i<count;i++){
TextView textView = new TextView(this);
textView.setId(ID+i);
final int currentItem = i;
textView.setBackgroundResource(R.drawable.white_cell);
textView.setPadding(padd,padd,padd,padd);
/// Converts 14 dip into its equivalent px
int size = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, getResources().getDisplayMetrics());
textView.setTextSize(size);
textView.setGravity(Gravity.CENTER);
/// Converts 14 dip into its equivalent px
int px = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30, getResources().getDisplayMetrics());
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(px, px);
linearLayout.addView(textView,params);
}
((TextView)linearLayout.getChildAt(0)).setText("1");
}
}
activity_main.xml
<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="match_parent"
tools:context=".MainActivity" >
<android.support.v4.view.ViewPager
android:id="#+id/viewPager"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true" >
</android.support.v4.view.ViewPager>
<LinearLayout
android:id="#+id/indicator_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="19dp" >
</LinearLayout>
</RelativeLayout>
item_view.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="100dp"
android:layout_height="match_parent"
android:id="#+id/root_view"
android:orientation="vertical" >
<TextView
android:id="#+id/textView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Text"
android:textAppearance="?android:attr/textAppearanceLarge" />
<Button
android:id="#+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="click me" />
</LinearLayout>
Current screen
expected screen
For one app I implemented similar the following way, with standard ViewPager:
Make pages full-screen with the actual content in an inner layout. For example, make the full-screen layout a RelativeLayout with transparent background and the actual content another RelativeLayout centered in the parent. If I remember right, the reason for this was that with just the inner layout as a page, the ViewPager would not have taken all the screen width on some devices such as Galaxy Nexus.
Use ViewPager.setPageMargin() to set up a negative page margin i.e. how much of the next/previous page you want to show. Make sure it only overlaps the transparent region of the parent full-screen layout.
Call ViewPager.setOffscreenPageLimit() to adjust the off-screen page count to at least 2 from the default 1 to ensure smooth paging by really creating the pages off-screen. Otherwise you will see next/previous pages being drawn while already partially showing on screen.
For anyone upset that the OP didn't update his question with the solution here is a link that explains, with minimal effort, how to pull this off in XML: http://blog.neteril.org/blog/2013/10/14/android-tip-viewpager-with-protruding-children/
Basically when you declare your viewpager in XML, give it the same left and right padding and set android:clipToPadding="false". (The clipToPadding is missing in his xml sample and necessary to achieve this effect)
Finally, I have added my solution for this question in GitHub. I have done some pretty tricks to get the workaround solution. You can get the project from the below link(Actually I have planned to create a blog with the explanation , but I dint have that much time to do).
Here is the link(https://github.com/noundla/Sunny_Projects/tree/master/CenterLockViewPager)
You have to copy the files from com.noundla.centerviewpagersample.comps package to your project. And you can see the usage of that Viewpager in MainActivity class.
Please let me know if anyone has problems with this.
I found solution in this post, below the code i used:
// Offset between sibling pages in dp
int pageOffset = 20;
// Visible part of sibling pages at the edges in dp
int sidePageVisibleWidth = 10;
// Horizontal padding will be
int horPadding = pageOffset + sidePageVisibleWidth;
// Apply parameters
viewPager.setClipToPadding(false);
viewPager.setPageMargin(UIUtil.dpToPx(pageOffset, getContext()));
viewPager.setPadding(UIUtil.dpToPx(horPadding, getContext()), 0, UIUtil.dpToPx(horPadding, getContext()), 0);
dpToPx code:
public static int dpToPx(int dp, Context context) {
float density = context.getResources().getDisplayMetrics().density;
return Math.round((float) dp * density);
}
This is all you need
You can use padding for viewPager and set clipToPadding false
Java
viewPager.setClipToPadding(false);
viewPager.setPadding(50, 0, 50, 0);
Kotlin
viewPager.clipToPadding = false
viewPager.setPadding(50, 0, 50, 0)
I had to center current page in view pager with different page widths, so solution with paddings was not suitable. Also user scrolling was disabled (it was tab bar view pager, scrolled by another view pager). Here is a very simple solution to do that - just override ViewPager.ScrollTo method just like this (C# code, Xamarin):
public override void ScrollTo(int x, int y)
{
x -= (int) (MeasuredWidth * (1 - Adapter.GetPageWidth(CurrentItem)) / 2);
base.ScrollTo(x, y);
}
And if you calculate page width for each fragment don't forget to cache them in array.
Extend HorizontalScrollView class as the parent for the scrolling view. In the onMeasure() method you can specify the width and height of each child. Little cumbersome way but the effect will be good and you can have a good hold on your child view.

Replacement for the linearLayout weights mechanism

Background:
Google suggests to avoid using nested weighted linearLayouts because of performance.
using nested weighted linearLayout is awful to read, write and maintain.
there is still no good alternative for putting views that are % of the available size. Only solutions are weights and using OpenGL. There isn't even something like the "viewBox" shown on WPF/Silverlight to auto-scale things.
This is why I've decided to create my own layout which you tell for each of its children exactly what should be their weights (and surrounding weights) compared to its size.
It seems I've succeeded , but for some reason I think there are some bugs which I can't track down.
One of the bugs is that textView, even though I give a lot of space for it, it puts the text on the top instead of in the center. imageViews on the other hand work very well. Another bug is that if I use a layout (for example a frameLayout) inside my customized layout, views within it won't be shown (but the layout itself will).
Please help me figure out why it occurs.
How to use: instead of the next usage of linear layout (I use a long XML on purpose, to show how my solution can shorten things):
<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="vertical">
<View android:layout_width="wrap_content" android:layout_height="0px"
android:layout_weight="1" />
<LinearLayout android:layout_width="match_parent"
android:layout_height="0px" android:layout_weight="1"
android:orientation="horizontal">
<View android:layout_width="0px" android:layout_height="wrap_content"
android:layout_weight="1" />
<TextView android:layout_width="0px" android:layout_weight="1"
android:layout_height="match_parent" android:text="#string/hello_world"
android:background="#ffff0000" android:gravity="center"
android:textSize="20dp" android:textColor="#ff000000" />
<View android:layout_width="0px" android:layout_height="wrap_content"
android:layout_weight="1" />
</LinearLayout>
<View android:layout_width="wrap_content" android:layout_height="0px"
android:layout_weight="1" />
</LinearLayout>
What I do is simply (the x is where to put the view itself in the weights list):
<com.example.weightedlayouttest.WeightedLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.example.weightedlayouttest"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" tools:context=".MainActivity">
<TextView android:layout_width="0px" android:layout_height="0px"
app:horizontalWeights="1,1x,1" app:verticalWeights="1,1x,1"
android:text="#string/hello_world" android:background="#ffff0000"
android:gravity="center" android:textSize="20dp" android:textColor="#ff000000" />
</com.example.weightedlayouttest.WeightedLayout>
My code of the special layout is:
public class WeightedLayout extends ViewGroup
{
#Override
protected WeightedLayout.LayoutParams generateDefaultLayoutParams()
{
return new WeightedLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT);
}
#Override
public WeightedLayout.LayoutParams generateLayoutParams(final AttributeSet attrs)
{
return new WeightedLayout.LayoutParams(getContext(),attrs);
}
#Override
protected ViewGroup.LayoutParams generateLayoutParams(final android.view.ViewGroup.LayoutParams p)
{
return new WeightedLayout.LayoutParams(p.width,p.height);
}
#Override
protected boolean checkLayoutParams(final android.view.ViewGroup.LayoutParams p)
{
final boolean isCorrectInstance=p instanceof WeightedLayout.LayoutParams;
return isCorrectInstance;
}
public WeightedLayout(final Context context)
{
super(context);
}
public WeightedLayout(final Context context,final AttributeSet attrs)
{
super(context,attrs);
}
public WeightedLayout(final Context context,final AttributeSet attrs,final int defStyle)
{
super(context,attrs,defStyle);
}
#Override
protected void onLayout(final boolean changed,final int l,final int t,final int r,final int b)
{
for(int i=0;i<this.getChildCount();++i)
{
final View v=getChildAt(i);
final WeightedLayout.LayoutParams layoutParams=(WeightedLayout.LayoutParams)v.getLayoutParams();
//
final int availableWidth=r-l;
final int totalHorizontalWeights=layoutParams.getLeftHorizontalWeight()+layoutParams.getViewHorizontalWeight()+layoutParams.getRightHorizontalWeight();
final int left=l+layoutParams.getLeftHorizontalWeight()*availableWidth/totalHorizontalWeights;
final int right=r-layoutParams.getRightHorizontalWeight()*availableWidth/totalHorizontalWeights;
//
final int availableHeight=b-t;
final int totalVerticalWeights=layoutParams.getTopVerticalWeight()+layoutParams.getViewVerticalWeight()+layoutParams.getBottomVerticalWeight();
final int top=t+layoutParams.getTopVerticalWeight()*availableHeight/totalVerticalWeights;
final int bottom=b-layoutParams.getBottomVerticalWeight()*availableHeight/totalVerticalWeights;
//
v.layout(left+getPaddingLeft(),top+getPaddingTop(),right+getPaddingRight(),bottom+getPaddingBottom());
}
}
// ///////////////
// LayoutParams //
// ///////////////
public static class LayoutParams extends ViewGroup.LayoutParams
{
int _leftHorizontalWeight =0,_rightHorizontalWeight=0,_viewHorizontalWeight=0;
int _topVerticalWeight =0,_bottomVerticalWeight=0,_viewVerticalWeight=0;
public LayoutParams(final Context context,final AttributeSet attrs)
{
super(context,attrs);
final TypedArray arr=context.obtainStyledAttributes(attrs,R.styleable.WeightedLayout_LayoutParams);
{
final String horizontalWeights=arr.getString(R.styleable.WeightedLayout_LayoutParams_horizontalWeights);
//
// handle horizontal weight:
//
final String[] words=horizontalWeights.split(",");
boolean foundViewHorizontalWeight=false;
int weight;
for(final String word : words)
{
final int viewWeightIndex=word.lastIndexOf('x');
if(viewWeightIndex>=0)
{
if(foundViewHorizontalWeight)
throw new IllegalArgumentException("found more than one weights for the current view");
weight=Integer.parseInt(word.substring(0,viewWeightIndex));
setViewHorizontalWeight(weight);
foundViewHorizontalWeight=true;
}
else
{
weight=Integer.parseInt(word);
if(weight<0)
throw new IllegalArgumentException("found negative weight:"+weight);
if(foundViewHorizontalWeight)
_rightHorizontalWeight+=weight;
else _leftHorizontalWeight+=weight;
}
}
if(!foundViewHorizontalWeight)
throw new IllegalArgumentException("couldn't find any weight for the current view. mark it with 'x' next to the weight value");
}
//
// handle vertical weight:
//
{
final String verticalWeights=arr.getString(R.styleable.WeightedLayout_LayoutParams_verticalWeights);
final String[] words=verticalWeights.split(",");
boolean foundViewVerticalWeight=false;
int weight;
for(final String word : words)
{
final int viewWeightIndex=word.lastIndexOf('x');
if(viewWeightIndex>=0)
{
if(foundViewVerticalWeight)
throw new IllegalArgumentException("found more than one weights for the current view");
weight=Integer.parseInt(word.substring(0,viewWeightIndex));
setViewVerticalWeight(weight);
foundViewVerticalWeight=true;
}
else
{
weight=Integer.parseInt(word);
if(weight<0)
throw new IllegalArgumentException("found negative weight:"+weight);
if(foundViewVerticalWeight)
_bottomVerticalWeight+=weight;
else _topVerticalWeight+=weight;
}
}
if(!foundViewVerticalWeight)
throw new IllegalArgumentException("couldn't find any weight for the current view. mark it with 'x' next to the weight value");
}
//
arr.recycle();
}
public LayoutParams(final int width,final int height)
{
super(width,height);
}
public LayoutParams(final ViewGroup.LayoutParams source)
{
super(source);
}
public int getLeftHorizontalWeight()
{
return _leftHorizontalWeight;
}
public void setLeftHorizontalWeight(final int leftHorizontalWeight)
{
_leftHorizontalWeight=leftHorizontalWeight;
}
public int getRightHorizontalWeight()
{
return _rightHorizontalWeight;
}
public void setRightHorizontalWeight(final int rightHorizontalWeight)
{
if(rightHorizontalWeight<0)
throw new IllegalArgumentException("negative weight :"+rightHorizontalWeight);
_rightHorizontalWeight=rightHorizontalWeight;
}
public int getViewHorizontalWeight()
{
return _viewHorizontalWeight;
}
public void setViewHorizontalWeight(final int viewHorizontalWeight)
{
if(viewHorizontalWeight<0)
throw new IllegalArgumentException("negative weight:"+viewHorizontalWeight);
_viewHorizontalWeight=viewHorizontalWeight;
}
public int getTopVerticalWeight()
{
return _topVerticalWeight;
}
public void setTopVerticalWeight(final int topVerticalWeight)
{
if(topVerticalWeight<0)
throw new IllegalArgumentException("negative weight :"+topVerticalWeight);
_topVerticalWeight=topVerticalWeight;
}
public int getBottomVerticalWeight()
{
return _bottomVerticalWeight;
}
public void setBottomVerticalWeight(final int bottomVerticalWeight)
{
if(bottomVerticalWeight<0)
throw new IllegalArgumentException("negative weight :"+bottomVerticalWeight);
_bottomVerticalWeight=bottomVerticalWeight;
}
public int getViewVerticalWeight()
{
return _viewVerticalWeight;
}
public void setViewVerticalWeight(final int viewVerticalWeight)
{
if(viewVerticalWeight<0)
throw new IllegalArgumentException("negative weight :"+viewVerticalWeight);
_viewVerticalWeight=viewVerticalWeight;
}
}
}
I accepted your challenge and attempted to create the layout you describe in response to my comment. You are right. It is surprisingly difficult to accomplish. Besides that, I do like shooting house flies. So I jumped on board and came up with this solution.
Extend the existing layout classes rather than creating your own from scratch. I went with RelativeLayout to start with but the same approach can be used by all of them. This gives you the ability to use the default behavior for that layout on child views that you don't want to manipulate.
I added four attributes to the layout called top, left, width and height. My intention was to mimic HTML by allowing values such as "10%", "100px", "100dp" etc.. At this time the only value accepted is an integer representing the % of parent. "20" = 20% of the layout.
For better performance I allow the super.onLayout() to execute through all of it's iterations and only manipulate the views with the custom attributes on it's last pass. Since these views will be positioned and scaled independently of the siblings we can move them after everything else has settled.
Here is atts.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="HtmlStyleLayout">
<attr name="top" format="integer"/>
<attr name="left" format="integer"/>
<attr name="height" format="integer"/>
<attr name="width" format="integer"/>
</declare-styleable>
</resources>
Here is my layout class.
package com.example.helpso;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.RelativeLayout;
public class HtmlStyleLayout extends RelativeLayout{
private int pass =0;
#Override
protected HtmlStyleLayout.LayoutParams generateDefaultLayoutParams()
{
return new HtmlStyleLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT,RelativeLayout.LayoutParams.WRAP_CONTENT);
}
#Override
public HtmlStyleLayout.LayoutParams generateLayoutParams(final AttributeSet attrs)
{
return new HtmlStyleLayout.LayoutParams(getContext(),attrs);
}
#Override
protected RelativeLayout.LayoutParams generateLayoutParams(final android.view.ViewGroup.LayoutParams p)
{
return new HtmlStyleLayout.LayoutParams(p.width,p.height);
}
#Override
protected boolean checkLayoutParams(final android.view.ViewGroup.LayoutParams p)
{
final boolean isCorrectInstance=p instanceof HtmlStyleLayout.LayoutParams;
return isCorrectInstance;
}
public HtmlStyleLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setScaleType(View v){
try{
((ImageView) v).setScaleType (ImageView.ScaleType.FIT_XY);
}catch (Exception e){
// The view is not an ImageView
}
}
#Override
protected void onLayout(final boolean changed,final int l,final int t,final int r,final int b)
{
super.onLayout(changed, l, t, r, b); //Let the parent layout do it's thing
pass++; // After the last pass of
final int childCount = this.getChildCount(); // the parent layout
if(true){ // we do our thing
for(int i=0;i<childCount;++i)
{
final View v=getChildAt(i);
final HtmlStyleLayout.LayoutParams params = (HtmlStyleLayout.LayoutParams)v.getLayoutParams();
int newTop = v.getTop(); // set the default value
int newLeft = v.getLeft(); // of these to the value
int newBottom = v.getBottom(); // set by super.onLayout()
int newRight= v.getRight();
boolean viewChanged = false;
if(params.getTop() >= 0){
newTop = ( (int) ((b-t) * (params.getTop() * .01)) );
viewChanged = true;
}
if(params.getLeft() >= 0){
newLeft = ( (int) ((r-l) * (params.getLeft() * .01)) );
viewChanged = true;
}
if(params.getHeight() > 0){
newBottom = ( (int) ((int) newTop + ((b-t) * (params.getHeight() * .01))) );
setScaleType(v); // set the scale type to fitxy
viewChanged = true;
}else{
newBottom = (newTop + (v.getBottom() - v.getTop()));
Log.i("heightElse","v.getBottom()=" +
Integer.toString(v.getBottom())
+ " v.getTop=" +
Integer.toString(v.getTop()));
}
if(params.getWidth() > 0){
newRight = ( (int) ((int) newLeft + ((r-l) * (params.getWidth() * .01))) );
setScaleType(v);
viewChanged = true;
}else{
newRight = (newLeft + (v.getRight() - v.getLeft()));
}
// only call layout() if we changed something
if(viewChanged)
Log.i("SizeLocation",
Integer.toString(i) + ": "
+ Integer.toString(newLeft) + ", "
+ Integer.toString(newTop) + ", "
+ Integer.toString(newRight) + ", "
+ Integer.toString(newBottom));
v.layout(newLeft, newTop, newRight, newBottom);
}
pass = 0; // reset the parent pass counter
}
}
public class LayoutParams extends RelativeLayout.LayoutParams
{
private int top, left, width, height;
public LayoutParams(final Context context, final AttributeSet atts) {
super(context, atts);
TypedArray a = context.obtainStyledAttributes(atts, R.styleable.HtmlStyleLayout);
top = a.getInt(R.styleable.HtmlStyleLayout_top , -1);
left = a.getInt(R.styleable.HtmlStyleLayout_left, -1);
width = a.getInt(R.styleable.HtmlStyleLayout_width, -1);
height = a.getInt(R.styleable.HtmlStyleLayout_height, -1);
a.recycle();
}
public LayoutParams(int w, int h) {
super(w,h);
Log.d("lp","2");
}
public LayoutParams(ViewGroup.LayoutParams source) {
super(source);
Log.d("lp","3");
}
public LayoutParams(ViewGroup.MarginLayoutParams source) {
super(source);
Log.d("lp","4");
}
public int getTop(){
return top;
}
public int getLeft(){
return left;
}
public int getWidth(){
return width;
}
public int getHeight(){
return height;
}
}
}
Here is an example activity xml
<com.example.helpso.HtmlStyleLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:html="http://schemas.android.com/apk/res/com.example.helpso"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:id="#+id/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:scaleType="fitXY"
android:src="#drawable/bg" />
<ImageView
android:id="#+id/imageView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/overlay"
html:height="10"
html:left="13"
html:top="18"
html:width="23" />
</com.example.helpso.HtmlStyleLayout>
Here are the images I used for testing.
If you do not set a value for a particular attribute it's default will be used. So if you set width but not height the image will scale in width and wrap_content for height.
Zipped project folder.
apk
I found the source of the bug. The problem is that I was using the layout's child count as in indicator of how many calls to onLayout it will make. This doesn't seem to hold true in older versions of android. I noticed in 2.1 onLayout is only called once. So I changed
if(pass == childCount){
to
if(true){
and it started working as expected.
I still thinks it's beneficial to adjust the layout only after the super is done. Just need to find a better way to know when that is.
EDIT
I didn't realize that your intention was to patch together images with pixel by pixel precision. I achieved the precision you are looking for by using double float precision variables instead of integers. However, you will not be able accomplish this while allowing your images to scale. When an images is scaled up pixels are added at some interval between the existing pixels. The color of the new pixels are some weighted average of the surrounding pixels. When you scale the images independently of each other they don't share any information. The result is that you will always have some artifact at the seam. Add to that the result of rounding since you can't have a partial pixel and you will always have a +/-1 pixel tolerance.
To verify this you can attempt the same task in your premium photo editing software. I use PhotoShop. Using the same images as in my apk, I placed them in seperate files. I scaled them both by 168% vertically and 127% horizontally. I then placed them in a file together and attempted to align them. The result was exactly the same as is seen in my apk.
To demonstrate the accuracy of the layout, I added a second activity to my apk. On this activity I did not scale the background image. Everything else is exactly the same. The result is seamless.
I also added a button to show/hide the overlay image and one to switch between the activities.
I updated both the apk and the zipped project folder on my google drive. You can get them by the links above.
After trying your code, I just find the reason of the problems you mentioned, and it is because in your customed layout, you only layout the child properly, however you forgot to measure your child properly, which will directly affect the drawing hierarchy, so simply add the below code, and it works for me.
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSize = MeasureSpec.getSize(widthMeasureSpec)-this.getPaddingRight()-this.getPaddingRight();
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec)-this.getPaddingTop()-this.getPaddingBottom();
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if(heightMode == MeasureSpec.UNSPECIFIED || widthMode == MeasureSpec.UNSPECIFIED)
throw new IllegalArgumentException("the layout must have a exact size");
for (int i = 0; i < this.getChildCount(); ++i) {
View child = this.getChildAt(i);
LayoutParams lp = (LayoutParams)child.getLayoutParams();
int width = lp._viewHorizontalWeight * widthSize/(lp._leftHorizontalWeight+lp._rightHorizontalWeight+lp._viewHorizontalWeight);
int height = lp._viewVerticalWeight * heightSize/(lp._topVerticalWeight+lp._bottomVerticalWeight+lp._viewVerticalWeight);
child.measure(width | MeasureSpec.EXACTLY, height | MeasureSpec.EXACTLY);
}
this.setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec));
}
Now there is a nicer solution than the custom layout I've made:
PercentRelativeLayout
Tutorial can be found here and a repo can be found here.
Example code:
<android.support.percent.PercentRelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<ImageView
app:layout_widthPercent="50%"
app:layout_heightPercent="50%"
app:layout_marginTopPercent="25%"
app:layout_marginLeftPercent="25%"/>
</android.support.percent.PercentFrameLayout/>
or:
<android.support.percent.PercentFrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<ImageView
app:layout_widthPercent="50%"
app:layout_heightPercent="50%"
app:layout_marginTopPercent="25%"
app:layout_marginLeftPercent="25%"/>
</android.support.percent.PercentFrameLayout/>
I wonder though if it can handle the issues I've shown here.
I propose to use following optimizations:
<FrameLayout 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:gravity="center">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content" android:text="#string/hello_world"
android:background="#ffff0000" android:gravity="center"
android:textSize="20dp" android:textColor="#ff000000" />
</FrameLayout>
or use http://developer.android.com/reference/android/widget/LinearLayout.html#attr_android:weightSum
or use TableLayout with layout_weight for rows and columns
or use GridLayout.

Android:Why after override onMeasure() in a custom view, the view's text can't show in RalativeLayout?

I made a custom component that extends View and overrides its onMeasure(), the content of this component is some text, then I add it to a RelativeLayout, but this text can't display, if I comment onMeasure() that been overridden the text shows. What's the reason?
Here is the code:
public class CustomView extends View {
private String text;
private int viewWidth;
private int viewHeight;
private Paint paint;
private FontMetrics fontMetrics;
public CustomView(Context context) {
this(context, null);
}
public CustomView(Context context, String text) {
this(context, text, 0);
this.text = text;
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
updateViewBounds();
}
public CustomView(Context context, String text, int defStyle) {
super(context);
}
private void updateViewBounds(){
viewWidth = (int) paint.measureText(this.text);
fontMetrics = paint.getFontMetrics();
viewHeight = (int)(fontMetrics.descent - fontMetrics.ascent);
}
private String getText() {
return this.text;
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(viewWidth, viewHeight);
//setMeasuredDimension(560, 100);even though give a ensured size, it can't //anyway.
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
paint.setColor(Color.WHITE);
paint.setTextSize(30);
canvas.drawText(text, 0, 200, paint);
Log.e("content", ""+this.getText());
}
public boolean onTouchEvent (MotionEvent event){
Log.e("Touch", ""+this.getText());
return false;
}
}
Here is the Activity:
public class CustomViewActivity extends Activity {
/** Called when the activity is first created. */
private RelativeLayout contentLayout;
private CustomView view1;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
contentLayout = (RelativeLayout)findViewById(R.id.contentLayout);
view1 = new CustomView(this, "You drive me crazy!!!");
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
view1.setLayoutParams(layoutParams);
contentLayout.addView(view1);
}
}
this is the XML file:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/contentLayout"
android:layout_width="1024px"
android:layout_height="560px"
android:orientation="vertical" >
<Button
android:id="#+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_marginLeft="126dp"
android:text="Button" />
</RelativeLayout>
You can absolutely set the MeasureSpec to a different size, however, the arguments for onMeasure are misleading. A MeasureSpec is a specially translated int that has to be specifically created by using both a pixel measure and a flag. The correct way to set a specific size it indicated below...
final int desiredHSpec = MeasureSpec.makeMeasureSpec(pixelHeight, MeasureSpec.MODE_CONSTANT);
final int desiredWSpec = MeasureSpec.makeMeasureSpec(pixelWidth, MeasureSpec.MODE_CONSTANT);
setMeasuredDimension(desiredWSpec, desiredHSpec);
The MODE_CONSTANTS must have a value of one of the following:
* AT_MOST - meaning that it is dynamic, but will be clipped if the contents are too large
* EXACTLY - meaning it will be that size no matter how large or small the contents are
* UNSPECIFIED - meaning that it will make whatever decision it makes according to the parameters of the parents, children, device size, etc...
If you do not specify one of these constants, then the Android Layout rendering engine has no idea what to do, and simply hides the object. It must be understood, that as an open platform for so many devices, Google decided to make the layout engine "dynamic and intelligent" to support as many apps as possible on as many platforms as possible. This simply requires the developer to let the device know exactly what it needs.
Note: It sounds like you want EXACTLY, but think carefully about your choice and how many devices you will be supporting. :)

Categories

Resources