Header views are not what we want so I thought about inheriting from ListView and add the functionality but aber a look at the source I thought there must be an easier solution to this.
So my solution was to create a subclass of LinearLayout which intercepts touch events. There are two children. A container for the additional options at the top and the ListView of cause.
And this turned out to be an easy and nice solution IMHO.
Here is the source of that touch interception LinearLayout:
package de.mobilej.widget;
import android.content.Context;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;
import android.view.View;
import android.view.animation.OvershootInterpolator;
import android.widget.LinearLayout;
import android.widget.ListView;
import de.mobilej.listboxtest.R;
public class TopDrawer extends LinearLayout {
private boolean headerVisible;
private ListView lv;
private int originalLvBottom;
PointerCoords pointerMoveStart = new PointerCoords();
boolean scrolling = false;
private View v;
public TopDrawer(Context context) {
super(context);
}
public TopDrawer(Context context, AttributeSet attrs) {
super(context, attrs);
}
public TopDrawer(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (v == null) {
v = findViewById(R.id.header);
lv = (ListView) findViewById(R.id.mylist);
}
originalLvBottom = lv.getBottom();
if (!headerVisible) {
v.setTranslationY(-1 * v.getHeight());
lv.setTranslationY(-1 * v.getHeight());
lv.setBottom(originalLvBottom + v.getHeight());
}
}
public void colapse() {
headerVisible = false;
lv.setBottom(originalLvBottom + v.getHeight());
float curTrans = v.getTranslationY();
float destTrans = -1 * v.getHeight();
if (curTrans > destTrans) {
v.animate().translationY(destTrans).setDuration(300).setInterpolator(new OvershootInterpolator()).start();
lv.animate().translationY(destTrans).setDuration(300).setInterpolator(new OvershootInterpolator()).start();
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_DOWN) {
scrolling = true;
ev.getPointerCoords(0, pointerMoveStart);
} else if (action == MotionEvent.ACTION_MOVE && scrolling) {
PointerCoords currentPos = new PointerCoords();
ev.getPointerCoords(0, currentPos);
if (pointerMoveStart.y - currentPos.y < 0) {
if (lv.getFirstVisiblePosition() == 0) {
View topChild = lv.getChildAt(0);
if (topChild.getY() == 0) {
return true;
}
}
}
}
return false;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_MOVE && scrolling && !headerVisible) {
PointerCoords currentPos = new PointerCoords();
ev.getPointerCoords(0, currentPos);
if (pointerMoveStart.y - currentPos.y < 0) {
if (lv.getFirstVisiblePosition() == 0) {
View topChild = lv.getChildAt(0);
if (topChild.getY() == 0) {
int hHeader = v.getHeight();
int moved = (int) (currentPos.y - pointerMoveStart.y);
int trans = -hHeader + moved;
if (trans > 0) {
trans = 0;
headerVisible = true;
}
v.setTranslationY(trans);
lv.setTranslationY(trans);
lv.setBottom(originalLvBottom - trans);
return true;
}
}
}
} else if (action == MotionEvent.ACTION_UP) {
scrolling = false;
if (!headerVisible) {
lv.setBottom(originalLvBottom + v.getHeight());
float curTrans = v.getTranslationY();
float destTrans = -1 * v.getHeight();
if (curTrans > destTrans) {
v.animate().translationY(destTrans).setDuration(300).setInterpolator(new OvershootInterpolator())
.start();
lv.animate().translationY(destTrans).setDuration(300).setInterpolator(new OvershootInterpolator())
.start();
}
}
}
return false;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
Bundle b = (Bundle) state;
if (b == null) {
return;
}
super.onRestoreInstanceState(b.getParcelable("SUPER"));
headerVisible = b.getBoolean("visible");
}
@Override
protected Parcelable onSaveInstanceState() {
Bundle b = new Bundle();
b.putParcelable("SUPER", super.onSaveInstanceState());
b.putBoolean("visible", headerVisible);
return b;
}
}
It's not fully polished code but just a quick prove of concept. And it's also not possible currently to hide the additional views easily by e.g. overscrolling to the end of the list (in my example I have a button that calls the collape method). But all this can be added easily.
A possible layout file could look 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="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
<de.mobilej.widget.TopDrawer
android:id="@+id/topDrawer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<LinearLayout
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<EditText
android:id="@+id/myEditText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
<ImageButton
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@android:drawable/btn_star" />
</LinearLayout>
<ListView
android:id="@+id/mylist"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/textView1"
android:layout_marginTop="26dp" >
</ListView>
</de.mobilej.widget.TopDrawer>
</LinearLayout>
The important things here are the IDs header and myList (which are not wisely choosen I have to admit).
Wth this idea also things like the Pull-To-Refresh as seen in the latest GMail for Android version are now very easy to implement.
Thank you for sharing.
ReplyDeleteI don't stop wondering how many trick you can do knowing how to code. That is why I consider data room providers to be a great solution for business.