I am working on an app that has a homescreen. This homescreen should behave like the android homescreen where you can switch between several views by flinging your finger over the touch screen.
The solution is easy. I have 3 view instances, right, left and current view. I get this instances from the viewflipper that I initialized earlier. Since I have a HTC G1 my sreen is 320 px in width and 480 px in height.
Imagine you capture the down value of a action down motion event when you touch the screen. Then you move your finger and the screen should move in exactly the same way so you have to recalculate the view's position. It works for me so far but I am facing a strange problem. When you touch the right view without moving you finger but keeping it on the screen for less then a second the view disappears and shows the left view.
Here is my code:
public class MainActivity extends Activity implements OnTouchListener{
private ViewFlipper vf;
private float downXValue;
private View view1, view2, view3;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
this.vf = (ViewFlipper) findViewById(R.id.flipper);
if(this.vf != null){
this.view1 = vf.getChildAt(0);
this.view2 = vf.getChildAt(1);
this.view3 = vf.getChildAt(2);
vf.setDisplayedChild(0);
}
LinearLayout layMain = (LinearLayout) findViewById(R.id.layout_main);
layMain.setOnTouchListener((OnTouchListener) this);
}
public boolean onTouch(View v, MotionEvent arg1) {
final View currentView = vf.getCurrentView();
final View leftView, rightView;
if(currentView == view1){
leftView = view3;
rightView = view2;
}else if(currentView == view2){
leftView = view1;
rightView = view3;
}else if(currentView == view3){
leftView = view2;
rightView = view1;
}else{
leftView = null;
rightView = null;
}
switch (arg1.getAction()){
case MotionEvent.ACTION_DOWN:{
this.downXValue = arg1.getX();
break;
}
case MotionEvent.ACTION_UP:{
float currentX = arg1.getX();
if ((downXValue < currentX)){
if(currentView != view3){
float t3 = (320-(currentX-downXValue))/320;
this.vf.setInAnimation(AnimationHelper.inFromLeftAnimation(t3));
this.vf.setOutAnimation(AnimationHelper.outToRightAnimation(t3));
this.vf.showPrevious(); }
}
if ((downXValue > currentX)){
if(currentView != view2){
float t = (320-(downXValue-currentX))/320;
this.vf.setInAnimation(AnimationHelper.inFromRightAnimation(t));
this.vf.setOutAnimation(AnimationHelper.outToLeftAnimation(t));
this.vf.showNext();}
}
}
break;
case MotionEvent.ACTION_MOVE:{
leftView.setVisibility(View.VISIBLE);
rightView.setVisibility(View.VISIBLE);
float currentX = arg1.getX();
if(downXValue > currentX){
if(currentView != view2){
currentView.layout((int) (currentX - downXValue),
currentView.getTop(),
(int) (currentX - downXValue) + 320,
currentView.getBottom());
}
}
if(downXValue < currentX){
if(currentView != view3){
currentView.layout((int) (currentX - downXValue),
currentView.getTop(),
(int) (currentX - downXValue) + 320,
currentView.getBottom());
}
}
leftView.layout(currentView.getLeft()-320, leftView.getTop(),
currentView.getLeft(), leftView.getBottom());
rightView.layout(currentView.getRight(), rightView.getTop(),
currentView.getRight() + 320, rightView.getBottom());
}
}
return true;
}
public static class AnimationHelper {
public static Animation inFromRightAnimation(float param) {
Animation inFromRight = new TranslateAnimation(
Animation.RELATIVE_TO_PARENT, +param,
Animation.RELATIVE_TO_PARENT, 0.0f,
Animation.RELATIVE_TO_PARENT, 0.0f,
Animation.RELATIVE_TO_PARENT, 0.0f);
inFromRight.setDuration(250);
inFromRight.setInterpolator(new AccelerateInterpolator());
return inFromRight;
}
public static Animation outToLeftAnimation(float param) {
Animation outtoLeft = new TranslateAnimation(
Animation.RELATIVE_TO_PARENT, 0.0f,
Animation.RELATIVE_TO_PARENT, -param,
Animation.RELATIVE_TO_PARENT, 0.0f,
Animation.RELATIVE_TO_PARENT, 0.0f);
outtoLeft.setDuration(250);
outtoLeft.setInterpolator(new AccelerateInterpolator());
return outtoLeft;
}
// for the next movement
public static Animation inFromLeftAnimation(float param) {
Animation inFromLeft = new TranslateAnimation(
Animation.RELATIVE_TO_PARENT, -param,
Animation.RELATIVE_TO_PARENT, 0.0f,
Animation.RELATIVE_TO_PARENT, 0.0f,
Animation.RELATIVE_TO_PARENT, 0.0f);
inFromLeft.setDuration(250);
inFromLeft.setInterpolator(new AccelerateInterpolator());
return inFromLeft;
}
public static Animation outToRightAnimation(float param) {
Animation outtoRight = new TranslateAnimation(
Animation.RELATIVE_TO_PARENT, 0.0f,
Animation.RELATIVE_TO_PARENT, +param,
Animation.RELATIVE_TO_PARENT, 0.0f,
Animation.RELATIVE_TO_PARENT, 0.0f);
outtoRight.setDuration(250);
outtoRight.setInterpolator(new AccelerateInterpolator());
return outtoRight;
}
}
}
I think such a Homescreen is an interesting UI element.
Any ideas?
First, touch-and-hold an icon or a widget to grab it. Then, drag it to the right edge of your Android Home screen. When you do that, a new Home screen page is created, and you can place your item on it. Release the shortcut, folder, or widget anywhere you want on the new page.
You can create your own widget with the help of third-party apps. One such app is KWGT Kustom Widget Maker, and for the purpose of this tutorial, we are using it to design a simple event reminder widget that links to the Google Calendar.
EDIT (July 3rd, 2012):
Since there seem to be still quite a few views and comments about this answer, I thought I should add a note, that with the newer SDK, you should now use ViewPager instead to have the same functionality. That class is also included in the Android Support library so you can also use it to run on earlier Android devices.
EDIT (March 4th, 2013):
Since there are still people coming here, just wanted to also say I put together a ViewPager with the background moving at slower speed to give a parallax effect. The code is here.
If you really want to do it all manually, the original answer is here below...
I think you can find what you are looking for here : http://www.anddev.org/why_do_not_these_codes_work-t4012.html
I used that in a different project to also create a home screen with different views. This is straight from the Android Launcher, it works quite well after following that thread.
Here is my code... first the source code
package com.matthieu.launcher;
import android.content.Context;
import android.util.Log;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewConfiguration;
import android.widget.Scroller;
public class DragableSpace extends ViewGroup {
private Scroller mScroller;
private VelocityTracker mVelocityTracker;
private int mScrollX = 0;
private int mCurrentScreen = 0;
private float mLastMotionX;
private static final String LOG_TAG = "DragableSpace";
private static final int SNAP_VELOCITY = 1000;
private final static int TOUCH_STATE_REST = 0;
private final static int TOUCH_STATE_SCROLLING = 1;
private int mTouchState = TOUCH_STATE_REST;
private int mTouchSlop = 0;
public DragableSpace(Context context) {
super(context);
mScroller = new Scroller(context);
mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
this.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.FILL_PARENT));
}
public DragableSpace(Context context, AttributeSet attrs) {
super(context, attrs);
mScroller = new Scroller(context);
mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
this.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT ,
ViewGroup.LayoutParams.FILL_PARENT));
TypedArray a=getContext().obtainStyledAttributes(attrs,R.styleable.DragableSpace);
mCurrentScreen = a.getInteger(R.styleable.DragableSpace_default_screen, 0);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
/*
* This method JUST determines whether we want to intercept the motion.
* If we return true, onTouchEvent will be called and we do the actual
* scrolling there.
*/
/*
* Shortcut the most recurring case: the user is in the dragging state
* and he is moving his finger. We want to intercept this motion.
*/
final int action = ev.getAction();
if ((action == MotionEvent.ACTION_MOVE)
&& (mTouchState != TOUCH_STATE_REST)) {
return true;
}
final float x = ev.getX();
switch (action) {
case MotionEvent.ACTION_MOVE:
/*
* mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
* whether the user has moved far enough from his original down touch.
*/
/*
* Locally do absolute value. mLastMotionX is set to the y value
* of the down event.
*/
final int xDiff = (int) Math.abs(x - mLastMotionX);
boolean xMoved = xDiff > mTouchSlop;
if (xMoved) {
// Scroll if the user moved far enough along the X axis
mTouchState = TOUCH_STATE_SCROLLING;
}
break;
case MotionEvent.ACTION_DOWN:
// Remember location of down touch
mLastMotionX = x;
/*
* If being flinged and user touches the screen, initiate drag;
* otherwise don't. mScroller.isFinished should be false when
* being flinged.
*/
mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
// Release the drag
mTouchState = TOUCH_STATE_REST;
break;
}
/*
* The only time we want to intercept motion events is if we are in the
* drag mode.
*/
return mTouchState != TOUCH_STATE_REST;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
final int action = event.getAction();
final float x = event.getX();
switch (action) {
case MotionEvent.ACTION_DOWN:
Log.i(LOG_TAG, "event : down");
/*
* If being flinged and user touches, stop the fling. isFinished
* will be false if being flinged.
*/
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
// Remember where the motion event started
mLastMotionX = x;
break;
case MotionEvent.ACTION_MOVE:
// Log.i(LOG_TAG,"event : move");
// if (mTouchState == TOUCH_STATE_SCROLLING) {
// Scroll to follow the motion event
final int deltaX = (int) (mLastMotionX - x);
mLastMotionX = x;
//Log.i(LOG_TAG, "event : move, deltaX " + deltaX + ", mScrollX " + mScrollX);
if (deltaX < 0) {
if (mScrollX > 0) {
scrollBy(Math.max(-mScrollX, deltaX), 0);
}
} else if (deltaX > 0) {
final int availableToScroll = getChildAt(getChildCount() - 1)
.getRight()
- mScrollX - getWidth();
if (availableToScroll > 0) {
scrollBy(Math.min(availableToScroll, deltaX), 0);
}
}
// }
break;
case MotionEvent.ACTION_UP:
Log.i(LOG_TAG, "event : up");
// if (mTouchState == TOUCH_STATE_SCROLLING) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000);
int velocityX = (int) velocityTracker.getXVelocity();
if (velocityX > SNAP_VELOCITY && mCurrentScreen > 0) {
// Fling hard enough to move left
snapToScreen(mCurrentScreen - 1);
} else if (velocityX < -SNAP_VELOCITY
&& mCurrentScreen < getChildCount() - 1) {
// Fling hard enough to move right
snapToScreen(mCurrentScreen + 1);
} else {
snapToDestination();
}
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
// }
mTouchState = TOUCH_STATE_REST;
break;
case MotionEvent.ACTION_CANCEL:
Log.i(LOG_TAG, "event : cancel");
mTouchState = TOUCH_STATE_REST;
}
mScrollX = this.getScrollX();
return true;
}
private void snapToDestination() {
final int screenWidth = getWidth();
final int whichScreen = (mScrollX + (screenWidth / 2)) / screenWidth;
Log.i(LOG_TAG, "from des");
snapToScreen(whichScreen);
}
public void snapToScreen(int whichScreen) {
Log.i(LOG_TAG, "snap To Screen " + whichScreen);
mCurrentScreen = whichScreen;
final int newX = whichScreen * getWidth();
final int delta = newX - mScrollX;
mScroller.startScroll(mScrollX, 0, delta, 0, Math.abs(delta) * 2);
invalidate();
}
public void setToScreen(int whichScreen) {
Log.i(LOG_TAG, "set To Screen " + whichScreen);
mCurrentScreen = whichScreen;
final int newX = whichScreen * getWidth();
mScroller.startScroll(newX, 0, 0, 0, 10);
invalidate();
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childLeft = 0;
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != View.GONE) {
final int childWidth = child.getMeasuredWidth();
child.layout(childLeft, 0, childLeft + childWidth, child
.getMeasuredHeight());
childLeft += childWidth;
}
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int width = MeasureSpec.getSize(widthMeasureSpec);
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
if (widthMode != MeasureSpec.EXACTLY) {
throw new IllegalStateException("error mode.");
}
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (heightMode != MeasureSpec.EXACTLY) {
throw new IllegalStateException("error mode.");
}
// The children are given the same width and height as the workspace
final int count = getChildCount();
for (int i = 0; i < count; i++) {
getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
}
Log.i(LOG_TAG, "moving to screen "+mCurrentScreen);
scrollTo(mCurrentScreen * width, 0);
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
mScrollX = mScroller.getCurrX();
scrollTo(mScrollX, 0);
postInvalidate();
}
}
}
And the layout file :
<?xml version="1.0" encoding="utf-8"?>
<com.matthieu.launcher.DragableSpace xmlns:app="http://schemas.android.com/apk/res/com.matthieu.launcher"
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/space"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
app:default_screen="1"
>
<include android:id="@+id/left" layout="@layout/left_screen" />
<include android:id="@+id/center" layout="@layout/initial_screen" />
<include android:id="@+id/right" layout="@layout/right_screen" />
</com.matthieu.launcher.DragableSpace>
To be able to have the extra attribute in the xml file, you want to save this in res/values/attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="DragableSpace">
<attr name="default_screen" format="integer"/>
</declare-styleable>
</resources>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With