I have created a basic custom view which replicates this behaviour, it's not completely the same but I think it's close enough for now, if it needs to be exactly the same this can be quickly achieved by modifying the updateChildViews()
method. I wrote this class in 20 minutes so it's far from perfect, for a production ready solution some additional work has to be done. Generally this solution works with all kinds of child views, but to replicate the exact behaviour use an ImageView as background for your child views and set this property on the ImageViews:
android:scaleType="centerCrop"
Problems I see with my solution in it's current state:
- Only works in vertical orientation
- No view recycling.
- Should be derived from AdapterView and not LinearLayout.
Anyway that's how it looks so far:
Here is the source code:
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
public class Accordion extends LinearLayout {
private static final String LOG_TAG = Accordion.class.getSimpleName();
private double scrollProgress = 0.0;
private double topViewScaleFactor = 2.0;
private double collapsedViewHeight = 200.0;
private double expandedViewHeight = 700.0;
private double scrollProgressPerView = expandedViewHeight;
private final ScrollTouchListener touchListener = new ScrollTouchListener() {
@Override
protected void onScroll(float x, float y) {
scrollProgress += y;
if(scrollProgress < 0.0) {
scrollProgress = 0.0;
}
int viewCount = getChildCount();
double maxScrollProgress = (viewCount - 1) * scrollProgressPerView + 1;
if(scrollProgress > maxScrollProgress) {
scrollProgress = maxScrollProgress;
}
Log.i(LOG_TAG, String.format("Scroll Progress: %f", scrollProgress));
updateChildViews();
}
};
public Accordion(Context context) {
super(context);
this.setOnTouchListener(this.touchListener);
}
public Accordion(Context context, AttributeSet attrs) {
super(context, attrs);
this.setOnTouchListener(this.touchListener);
}
public Accordion(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.setOnTouchListener(this.touchListener);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
updateChildViews();
}
private void updateChildViews() {
int viewCount = getChildCount();
double progress = scrollProgress;
double overflow = 0;
for(int i = 0; i < viewCount; i++) {
View child = getChildAt(i);
if(child != null) {
if(progress >= scrollProgressPerView) {
progress -= scrollProgressPerView;
child.setVisibility(View.GONE);
setChildHeight(child, 0);
} else if (progress > 0) {
setChildHeight(child, expandedViewHeight - progress);
overflow = progress;
child.setVisibility(View.VISIBLE);
progress = 0;
} else {
if(overflow > 0) {
double height = collapsedViewHeight + overflow;
if(height > expandedViewHeight) {
height = expandedViewHeight;
}
setChildHeight(child, height);
overflow = 0;
} else {
setChildHeight(child, i > 0 ? collapsedViewHeight : expandedViewHeight);
}
child.setVisibility(View.VISIBLE);
}
}
}
}
private void setChildHeight(View child, double height) {
child.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, (int)height));
}
private static abstract class ScrollTouchListener implements OnTouchListener {
private static final String LOG_TAG = ScrollTouchListener.class.getSimpleName();
private boolean scrolling = false;
private float x = 0;
private float y = 0;
@Override
public boolean onTouch(View view, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
x = event.getX();
y = event.getY();
scrolling = true;
return true;
case MotionEvent.ACTION_UP:
scrolling = false;
return true;
case MotionEvent.ACTION_MOVE:
if (scrolling) {
float newX = event.getX();
float newY = event.getY();
float difX = x - newX;
float difY = y - newY;
onScroll(difX, difY);
x = newX;
y = newY;
}
return true;
default:
return false;
}
}
protected abstract void onScroll(float x, float y);
}
}
To use it just put it in a layout like this:
<at.test.app.Accordion xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="centerCrop"
android:src="@drawable/alpen"/>
<ImageView android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="centerCrop"
android:src="@drawable/alpen"/>
<ImageView android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="centerCrop"
android:src="@drawable/alpen"/>
<ImageView android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="centerCrop"
android:src="@drawable/alpen"/>
</at.test.app.Accordion>
If you have any additional questions feel free to ask!
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…