版权声明:欢迎转载,转载请注明出处http://blog.youkuaiyun.com/qian_xiao_lj
实现两个ScrollView的同步显示。因为Android控件中没有此种功能,因此需要重写ScrollView。
思路介绍
我们首先想到使用ScrollView的类似与setOnScrollChangedListener的方法来实现,当一个ScrollView滚动时,触发该方法进而使另外一个ScrollView滚动。不过很遗憾,谷歌没有提供该方法。通过查询相应的源代码,我们发现该方法的原型
protected void onScrollChanged(int x, int y, int oldx, int oldy)
该方法是protected类型,不能直接调用,于是需要重新实现ScrollView。
具体实现
首先,定一个一个接口(ScrollViewListener.java):
public interface ScrollViewListener {
void onScrollChanged(ObservableScrollView scrollView, int x, int y, int oldx, int oldy);
}
我们需要重写ScrollView才能实现该接口,因此有下面的代码(ObservableScrollView.java):
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ScrollView;
public class ObservableScrollView extends ScrollView {
private ScrollViewListener scrollViewListener = null;
public ObservableScrollView(Context context) {
super(context);
}
public ObservableScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public ObservableScrollView(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);
}
}
}
接下来是界面的XML,这里是一个简单的Demo,如下(main.xml):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#ffffff"
android:orientation="horizontal" >
<com.devin.ObservableScrollView
android:id="@+id/scrollview1"
android:layout_width="400dp"
android:layout_height="wrap_content" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:layout_width="wrap_content"
android:layout_height="200dp"
android:layout_weight="1"
android:text="monday"
android:textColor="#000000" />
<TextView
android:layout_width="wrap_content"
android:layout_height="200dp"
android:layout_weight="1"
android:text="tuesday"
android:textColor="#000000" />
<TextView
android:layout_width="wrap_content"
android:layout_height="200dp"
android:layout_weight="1"
android:text="wednesday"
android:textColor="#000000" />
<TextView
android:layout_width="wrap_content"
android:layout_height="200dp"
android:layout_weight="1"
android:text="thursday"
android:textColor="#000000" />
<TextView
android:layout_width="wrap_content"
android:layout_height="200dp"
android:layout_weight="1"
android:text="friday"
android:textColor="#000000" />
<TextView
android:layout_width="wrap_content"
android:layout_height="200dp"
android:layout_weight="1"
android:text="saturday"
android:textColor="#000000" />
<TextView
android:layout_width="wrap_content"
android:layout_height="200dp"
android:layout_weight="1"
android:text="sunday"
android:textColor="#000000" />
</LinearLayout>
</com.devin.ObservableScrollView>
<com.devin.ObservableScrollView
android:id="@+id/scrollview2"
android:layout_width="400dp"
android:layout_height="wrap_content" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:layout_width="wrap_content"
android:layout_height="200dp"
android:layout_weight="1"
android:text="monday"
android:textColor="#000000" />
<TextView
android:layout_width="wrap_content"
android:layout_height="200dp"
android:layout_weight="1"
android:text="tuesday"
android:textColor="#000000" />
<TextView
android:layout_width="wrap_content"
android:layout_height="200dp"
android:layout_weight="1"
android:text="wednesday"
android:textColor="#000000" />
<TextView
android:layout_width="wrap_content"
android:layout_height="200dp"
android:layout_weight="1"
android:text="thursday"
android:textColor="#000000" />
<TextView
android:layout_width="wrap_content"
android:layout_height="200dp"
android:layout_weight="1"
android:text="friday"
android:textColor="#000000" />
<TextView
android:layout_width="wrap_content"
android:layout_height="200dp"
android:layout_weight="1"
android:text="saturday"
android:textColor="#000000" />
<TextView
android:layout_width="wrap_content"
android:layout_height="200dp"
android:layout_weight="1"
android:text="sunday"
android:textColor="#000000" />
</LinearLayout>
</com.devin.ObservableScrollView>
</LinearLayout>
最后是我们的主程调用(TestActivity.java):
<strong style="font-size: 14px;">import android.app.Activity;
import android.os.Bundle;
public class TestActivity extends Activity implements ScrollViewListener {
private ObservableScrollView scrollView1 = null;
private ObservableScrollView scrollView2 = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
scrollView1 = (ObservableScrollView) findViewById(R.id.scrollview1);
scrollView1.setScrollViewListener(this);
scrollView2 = (ObservableScrollView) findViewById(R.id.scrollview2);
scrollView2.setScrollViewListener(this);
}
public void onScrollChanged(ObservableScrollView scrollView, int x, int y, int oldx, int oldy) {
if(scrollView == scrollView1) {
scrollView2.scrollTo(x, y);
} else if(scrollView == scrollView2) {
scrollView1.scrollTo(x, y);
}
}
}
</strong><p style="font-size: 16px; font-weight: bold; margin-top: 0px; margin-bottom: 0.75em; line-height: 27.2000007629395px; text-indent: 1em; color: rgb(51, 51, 51); font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: rgb(254, 254, 254);">可嵌套的<span style="line-height: 27.2000007629395px;">ScrollView:</span></p><p style="font-size: 16px; font-weight: bold; margin-top: 0px; margin-bottom: 0.75em; line-height: 27.2000007629395px; text-indent: 1em; font-family: 'Helvetica Neue', Helvetica, Tahoma, Arial, STXihei, 'Microsoft YaHei', 微软雅黑, sans-serif; background-color: rgb(254, 254, 254);"></p><p align="left" style="font-size: 14px;"><span style="color: rgb(68, 68, 68);">实现一个继承自ScrollView</span>的自定义控件,可以设置坐标,大小,并且能够任意添加其他的view。由于ScrollView只允许有一个子控件,所以,我们可以在其中加入一个ViewGroup,然后,将要添加的view加入该ViewGroup中即可</p><p align="left" style="font-size: 14px;">下面是该类的代码<span style="font-weight: bold;">:</span></p><p align="left" style="font-size: 14px; font-weight: bold;"></p><pre name="code" class="java" style="font-size: 14px; font-weight: bold;">public class LionScrollView extends ScrollView {
private MainViewGroup mv;
private int subViewWidth = 0;
private int subViewHeight = 0;
private int x;
private int y;
private int width;
private int height;
public LionScrollView() {
super(MainView.mMainActivity);
this.mv = new MainViewGroup();
super.addView(mv);
}
public void addView(View child) {
if (child == null)
return;
this.mv.addView(child);
}
//This method should return false to intercept the touch event
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
//here you can add some setters
.....
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int count = this.mv.getChildCount();
for (int i = 0; i < count; i++) {
final View child = this.mv.getChildAt(i);
int x = (int) child.getX() + child.getMeasuredWidth();
int y = (int) child.getY() + child.getMeasuredHeight();
if (x > subViewWidth) {
subViewWidth = x; this.mv.setMinimumWidth(x);
}
if (y > subViewHeight) {
subViewHeight = y; this.mv.setMinimumHeight(y);
}
}
this.setLeft(this.x);
this.setRight(this.x + this.width);
this.setTop(this.y);
this.setBottom(this.y + this.height);
super.onLayout(changed, l, t, r, b);
}
}
横向ScrollView:
自定义Viewiw实现步骤:
1、继承要自定义View的类,并实现带有参数的构造方法
2、重写onMeasure(确定自定义View的大小)和onLayout(确定自定义View的位置)方法
关于HorizontalScrollView的滑动,我们可以用onScrollChanged来监听参数L:
打印日志时可以发现,当滚动条向左(画面向右滑动)的时候,L的值是逐渐增大的,所以我们可以通过它来作为动画的变化梯度值。
HorizontalScrollView.class
public class MyHorizontalScrollView extends HorizontalScrollView {
// 在HorizontalScrollView有个LinearLayout
private LinearLayout linearLayout;
// 菜单,内容页
private ViewGroup myMenu;
private ViewGroup myContent;
//菜单宽度
private int myMenuWidth;
// 屏幕宽度
private int screenWidth;
// 菜单与屏幕右侧的距离(dp)
private int myMenuPaddingRight = 50;
// 避免多次调用onMeasure的标志
private boolean once = false;
/**
* 自定义View需要实现带有Context、AttributeSet这2个参数的构造方法,否则自定 义参数会出错
* 当使用了自定义属性时,会调用此构造方法
*
* @param context
* @param attrs
*/
public MyHorizontalScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
// 获取屏幕宽度
WindowManager windowManager = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics outMetrics = new DisplayMetrics();
windowManager.getDefaultDisplay().getMetrics(outMetrics);
screenWidth = outMetrics.widthPixels;// 屏幕宽度
// 将dp转换px
myMenuPaddingRight = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 50, context.getResources()
.getDisplayMetrics());
}
/**
* 设置子View的宽高,决定自身View的宽高,每次启动都会调用此方法
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (!once) {//使其只调用一次
// this指的是HorizontalScrollView,获取各个元素
linearLayout = (LinearLayout) this.getChildAt(0);// 第一个子元素
myMenu = (ViewGroup) linearLayout.getChildAt(0);// HorizontalScrollView下LinearLayout的第一个子元素
myContent = (ViewGroup) linearLayout.getChildAt(1);// HorizontalScrollView下LinearLayout的第二个子元素
// 设置子View的宽高,高于屏幕一致
myMenuWidth=myMenu.getLayoutParams().width = screenWidth - myMenuPaddingRight;// 菜单的宽度=屏幕宽度-右边距
myContent.getLayoutParams().width = screenWidth;// 内容宽度=屏幕宽度
// 决定自身View的宽高,高于屏幕一致
// 由于这里的LinearLayout里只包含了Menu和Content所以就不需要额外的去指定自身的宽
once = true;
}
}
//设置View的位置,首先,先将Menu隐藏(在eclipse中ScrollView的画面内容(非滚动条)正数表示向左移,向上移)
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
//刚载入界面的时候隐藏Menu菜单也就是ScrollView向左滑动菜单自身的大小
if(changed){
this.scrollTo(myMenuWidth, 0);//向左滑动,相当于把右边的内容页拖到正中央,菜单隐藏
}
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
int action=ev.getAction();
switch (action) {
case MotionEvent.ACTION_UP:
int scrollX=this.getScrollX();//滑动的距离scrollTo方法里,也就是onMeasure方法里的向左滑动那部分
if(scrollX>=myMenuWidth/2){
this.smoothScrollTo(myMenuWidth,0);//向左滑动展示内容
}else{
this.smoothScrollTo(0, 0);
}
return true;
}
return super.onTouchEvent(ev);
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
Log.i("tuzi",l+"");
float scale = l * 1.0f / myMenuWidth; // 1 ~ 0
/**
* 区别1:内容区域1.0~0.7 缩放的效果 scale : 1.0~0.0 0.7 + 0.3 * scale
*
* 区别2:菜单的偏移量需要修改
*
* 区别3:菜单的显示时有缩放以及透明度变化 缩放:0.7 ~1.0 1.0 - scale * 0.3 透明度 0.6 ~ 1.0
* 0.6+ 0.4 * (1- scale) ;
*
*/
float rightScale = 0.7f + 0.3f * scale;
float leftScale = 1.0f - scale * 0.3f;
float leftAlpha = 0.6f + 0.4f * (1 - scale);
// 调用属性动画,设置TranslationX
ViewHelper.setTranslationX(myMenu, myMenuWidth * scale * 0.8f);
ViewHelper.setScaleX(myMenu, leftScale);
ViewHelper.setScaleY(myMenu, leftScale);
ViewHelper.setAlpha(myMenu, leftAlpha);
// 设置content的缩放的中心点
ViewHelper.setPivotX(myContent, 0);
ViewHelper.setPivotY(myContent, myContent.getHeight() / 2);
ViewHelper.setScaleX(myContent, rightScale);
ViewHelper.setScaleY(myContent, rightScale);
}
}