这个名字是随便取的,效果就是一个可滚动的scrollview,他的背景可以随内容的滚动而同步滚动,但是滚动的速度不一样,从而有点视差上的错落感觉。今天看到一个这样的效果,觉得挺好的,于是自己动手写了一个,很简单,直接上代码。
MyParallaxScrollViewWidget:
这个类主要用来完成视差滚动的
package com.example.parallaxscroll;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import com.example.mypractice.R;
/**
* @author KingKong
* 视差滚动布局
*/
public class MyParallaxScrollView extends LinearLayout{
public MyParallaxScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyParallaxScrollView(Context context) {
super(context);
}
/**
* background和foreground滚动的视差参数
*/
private float mDiffFactor = 0;
/**
* child scrollview
*/
private ParallaxInnerScrollView mForeground;
/**
* background
*/
private View mBackgournd;
/**
* content
*/
private LinearLayout mContent;
/**
* 视差滚动根布局
*/
private View mParallaxScrollWidget;
/**
* 背景显示
*/
private View mBackgroundContent;
/**
* 初始化
*/
private void init() {
LayoutInflater inflater = LayoutInflater.from(getContext());
inflater.inflate(R.layout.my_parallax_scrollview, this);
mParallaxScrollWidget = findViewById(R.id.parallax_scroll_widget);
mBackgournd = findViewById(R.id.background);
mBackgroundContent = findViewById(R.id.background_content);
mForeground = (ParallaxInnerScrollView) findViewById(R.id.foreground);
mForeground.setOnScrollListener(onScrollListener);
mContent = (LinearLayout) findViewById(R.id.content);
transferChildren();
}
/**
* 移动子view,将布局文件中设定的子view移动可滚动的view中
*/
private void transferChildren() {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(0);
if (child != mParallaxScrollWidget) {
detachFromParent(child);
mContent.addView(child);
}
}
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
if (isInEditMode()) {
return;
}
init();
}
/**
* 添加child到可滚动的视图当中
* @param child child
*/
public void addChildView(View child) {
detachFromParent(child);
mContent.addView(child);
}
/**
* 添加child到可滚动的视图当中
* @param child child
* @param index index
*/
public void addChildView(View child, int index) {
detachFromParent(child);
mContent.addView(child, index);
}
/**
* 添加child到可滚动的视图当中
* @param child child
* @param width width
* @param height height
*/
public void addChildView(View child, int width, int height) {
detachFromParent(child);
mContent.addView(child, width, height);
}
/**
* detach from parent
* @param childView
*/
private void detachFromParent(View childView) {
if (null != childView) {
ViewGroup parent = (ViewGroup) childView.getParent();
if (null != parent) {
parent.removeView(childView);
}
}
}
/**
* 设置背景
* @param drawableId
*/
public void setBackgroundDrawableId(int drawableId) {
if (null != mBackgroundContent && drawableId >= 0) {
mBackgroundContent.setBackgroundResource(drawableId);
}
}
/* (non-Javadoc)
* @see android.view.View#setBackgroundDrawable(android.graphics.drawable.Drawable)
*/
public void setBackgroundDrawable(Drawable drawable) {
if (null != drawable && null != mBackgroundContent) {
mBackgroundContent.setBackgroundDrawable(drawable);
}
}
/**
* 设置前景
* @param drawableId
*/
public void setForegroundDrawableId(int drawableId) {
if (null != mForeground && drawableId >= 0) {
mForeground.setBackgroundResource(drawableId);
}
}
/**
* 设置前景
* @param drawable
*/
public void setForegroundDrawable(Drawable drawable) {
if (null != mForeground && null != drawable) {
mForeground.setBackgroundDrawable(drawable);
}
}
/**
* 设置视差滚动参数
* @param diffFactor
*/
public void setDiffFactor(float diffFactor) {
this.mDiffFactor = diffFactor;
invalidate();
}
/**
* background相对于屏幕可以滚动的距离
*/
private int mBackgroundDiff;
/**
* foreground内容相对于屏幕可以滚动的距离
*/
private int mForegroundDiff;
/**
* 视差因子,用于根据foreground滚动的距离来计算background的滚动
*/
private float mParallaxFactor;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (isInEditMode()) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
//先measure一次,以得到child的尺寸
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int foregroundTotalHeight = getChildScrollViewTotalHeight();
//计算需要额外布局的高度
int scrollDiff = computeScrollDiffDistance(mBackgournd.getMeasuredHeight(), foregroundTotalHeight);
int newBackgroundHeight = mBackgournd.getMeasuredHeight() + scrollDiff;
//重新measure一下background
measureChild(mBackgournd, widthMeasureSpec, MeasureSpec.makeMeasureSpec(newBackgroundHeight, MeasureSpec.EXACTLY));
mBackgroundDiff = mBackgournd.getMeasuredHeight() - getMeasuredHeight();
mForegroundDiff = foregroundTotalHeight - getMeasuredHeight();
mParallaxFactor = ((float) mBackgroundDiff) / ((float) mForegroundDiff);
}
/**
* 计算滚动差
* @param backgroundHeight
* @param forgroundHeight
* @return 背景需要额外增加的高度
*/
private int computeScrollDiffDistance(int backgroundHeight, int forgroundHeight) {
if (forgroundHeight <= backgroundHeight) {
return 0;
}
int heightDiff = forgroundHeight - backgroundHeight;
int scrollDiff = (int) (heightDiff * mDiffFactor);
return scrollDiff;
}
/**
* 返回scrollview 内容高度(在onMeasure中或者结束后调用)
* @return child scroll view的总高度
*/
private int getChildScrollViewTotalHeight() {
if (null == mContent) {
return 0;
}
return mContent.getMeasuredHeight();
}
/**
* forground滚动监听
*/
private InnerScrollViewScrollListener onScrollListener = new InnerScrollViewScrollListener() {
@Override
public void onScroll() {
int backgroundScrollY = (int) (mParallaxFactor * mForeground.getScrollY());
mBackgournd.scrollTo(0, backgroundScrollY);
}
};
/**
* @author yuanxingzhong
* 监听forground 的滚动
*/
public static interface InnerScrollViewScrollListener {
public void onScroll();
}
}
其对应的布局如下:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/parallax_scroll_widget"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<FrameLayout
android:id="@+id/background"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<View
android:id="@+id/background_content"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
<com.example.parallaxscroll.ParallaxInnerScrollView
android:id="@+id/foreground"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal|top"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:background="#ffffff"
android:scrollbars="none" >
<LinearLayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal|top"
android:orientation="vertical" >
</LinearLayout>
</com.example.parallaxscroll.ParallaxInnerScrollView>
</FrameLayout>
布局中有一个自定义的ScrollView,之所以自定义是为了监听它的滚动状态,因为ScrollView不能监听它是否在滚动,当然,如果你内部不用ScrollView,直接用Listview就更方便了。这个自定义的ScrollView如下:
package com.example.parallaxscroll;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ScrollView;
import com.example.parallaxscroll.MyParallaxScrollView.InnerScrollViewScrollListener;
/**
* @author KingKong
* 主要目的是对于ScrollView和HorizontalScrollView没有现成的接口来监听滚动
*/
public class ParallaxInnerScrollView extends ScrollView{
/**
* 滚动监听
*/
private InnerScrollViewScrollListener mScrollListener;
/**
* 设置滚动监听
* @param listener
*/
public void setOnScrollListener(InnerScrollViewScrollListener listener) {
mScrollListener = listener;
}
public ParallaxInnerScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public ParallaxInnerScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ParallaxInnerScrollView(Context context) {
super(context);
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (null != mScrollListener) {
mScrollListener.onScroll();
}
}
}
到这里就大功告成了!
附上一个测试demo:
MainActivity:
package com.example.mypractice;
import java.util.ArrayList;
import android.graphics.drawable.ColorDrawable;
import com.example.parallaxscroll.MyParallaxScrollView;
public class MainActivity extends ActionBarActivity {
private ArrayList<MyExpandableItem> mItems = new ArrayList<MyExpandableItem>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.parallax_scrollview_test);
MyParallaxScrollView parallaxScrollView = (MyParallaxScrollView) findViewById(R.id.my_parallax_scrollview);
parallaxScrollView.setBackgroundDrawableId(R.drawable.background); //设置背景
parallaxScrollView.setForegroundDrawable(new ColorDrawable(android.R.color.transparent)); //设置前景
parallaxScrollView.setDiffFactor(0.3f);
}
}
布局文件parallax_scrollview_test:
<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" >
<com.example.parallaxscroll.MyParallaxScrollView
android:id="@+id/my_parallax_scrollview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@string/content"
android:background="#ffffff"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@string/content"
android:background="#ffffff"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:background="#ffffff"
android:text="@string/content" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:background="#ffffff"
android:text="@string/content" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:background="#ffffff"
android:text="@string/content" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:background="#ffffff"
android:text="@string/content" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:background="#ffffff"
android:text="@string/content" />
</com.example.parallaxscroll.MyParallaxScrollView>
</LinearLayout>
上两张图吧,不是动态的,可能看不出效果。注意看后面背景的上下变化: