众所周知 现在大量的内容类型的应用都会用到ScrollView或ListView来呈现内容 甚至将其放在首页 ,那么如何让这个ScrollView的操作足够吸引人那就成了需要考虑的一个大问题 ,今天博主就来教你们如何做出一个强大的ScrollView

1.Element Relative
之所以提到这个是因为 大部分Scroll都是可以带动其他的一些控件动画从而呈现出良好的视觉效果,所以我们必须注重Elements之间的关系。既然是要用它来控制其他控件,那我们就必须获取他的一些滑动数据,所以我们选择重写ScrollView并且暴露一些回调接口。

我们要暴露的接口有:

onOverScroll(float distance); 监听滑动到顶之后继续滑动的事件,并且传入一个继续滑动的距离
onOverScrollEnd(); 过滑动之后释放的回调
//这两个用来处理滑动到顶继续滑动的下拉刷新事件
onScroll(float relLast,int relStart); 监听滑动过程,传入参数1值触发时相对上一次触发距离,2表示相对开始点距离

将这些接口封装如下:

public interface OnScrollListener{
public void onOverScroll(float overScroll);
public void onOverScrollEnd();
public void onScroll(float relLast,int relStart);
}

然后在重写的Scroll中完成这些事件的调用:(主要在onTouchEvent中实现)

public class ScrollListen extends ScrollView {
int lastScrollY; //上次的scroll 用来实现相对数值计算
int flag=0; //用来防止惯性滑动到底回弹的时候继续调用onScroll 影响控件动画
int downY; //按下时候的ScrollY
float downYs; //按下时候的rawY 因为scroll不会计算为负值的情况,所以为了实现overScroll我们把这个分出来
boolean canOverScroll; //是否可以OverScroll 只有当down的时候scroll为0才可以 否则不会触发onOverScroll和onOverscrollEnd
OnScrollListener onScrollListener; //接口
public ScrollListen(Context context, AttributeSet attrs){super(context, attrs);}//构造函数

/*
*fuction : setOnScrollListener
*
*设置onScrollListener
*/
public void setOnScrollListener(OnScrollListener on){
onScrollListener=on;
}

/*
* Handle :
* 处理惯性滑动时的onScroll事件
*/
private Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
int scrollY = ScrollListen.this.getScrollY();
if(onScrollListener != null){
if(flag==0){
if(scrollY-lastScrollY>0) flag=1;
else flag=-1;
}
int s=scrollY-lastScrollY;
if((flag==1&&s>0)||(flag==-1&&s<0)) onScrollListener.onScroll(s,scrollY); //防止惯性到底回弹影响结果 } //此时的距离和记录下的距离不相等,在隔5毫秒给handler发送消息 if(lastScrollY != scrollY){ lastScrollY = scrollY; handler.sendMessageDelayed(handler.obtainMessage(), 5); }else{ if(onScrollListener != null){ flag=0; } } }; }; /* * onTouchEvnet * 处理各种事件的回调 */ @Override public boolean onTouchEvent(MotionEvent ev) { if(onScrollListener != null) { onScrollListener.onScroll(this.getScrollY() - lastScrollY, lastScrollY = this.getScrollY()); switch (ev.getAction()) { case MotionEvent.ACTION_MOVE: if (canOverScroll) { float overSc = ev.getRawY() - downYs; if (overSc > 0) onScrollListener.onOverScroll(overSc);
//过滑动事件触发
}
break;
case MotionEvent.ACTION_DOWN:
downY = this.getScrollY();
downYs = ev.getRawY();
if (downY == 0) canOverScroll = true;
else canOverScroll = false;
//如果不加入第二行容易造成还在惯性滑动 没有将conOverScroll重置 动画一直播放
//记录数据并且判断是否可以过滑动
break;
case MotionEvent.ACTION_UP:
if (canOverScroll && onScrollListener != null) {
float overSc = ev.getRawY() – downYs;
if (overSc > 0) onScrollListener.onOverScrollEnd();
}
canOverScroll = false;
handler.sendMessageDelayed(handler.obtainMessage(), 5);
// 处理抬起事件
break;
}
}
return super.onTouchEvent(ev);
}

public interface OnScrollListener{
public void onOverScroll(float overScroll);
public void onOverScrollEnd();
public void onScroll(int relLast,int relStart);
}
}

接下来我们用这些回调来处理其他控件:
MainActivity.java activity_main.xml
首先简单的布局

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”
tools:context=”tecotaku.cn.myapplication.MainActivity”>
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:id=”@+id/scrollView”>
android:orientation=”vertical”
android:layout_width=”match_parent”
android:layout_height=”match_parent”>

android:layout_width=”match_parent”
android:layout_height=”140dp” />

android:layout_width=”match_parent”
android:layout_height=”60dp”
android:gravity=”center”
android:text=”New Text1″
android:layout_below=”@+id/linearLayoutmain”
android:layout_alignStart=”@+id/scrollView” />
android:layout_width=”match_parent”
android:layout_height=”60dp”
android:gravity=”center”
android:text=”New Text2″
android:layout_below=”@+id/linearLayoutmain”
android:layout_alignStart=”@+id/scrollView” />
android:layout_width=”match_parent”
android:layout_height=”60dp”
android:gravity=”center”
android:text=”New Text3″
android:layout_below=”@+id/linearLayoutmain”
android:layout_alignStart=”@+id/scrollView” />
android:layout_width=”match_parent”
android:layout_height=”60dp”
android:gravity=”center”
android:text=”New Text4″
android:layout_below=”@+id/linearLayoutmain”
android:layout_alignStart=”@+id/scrollView” />
android:layout_width=”match_parent”
android:layout_height=”60dp”
android:gravity=”center”
android:text=”New Text5″
android:layout_below=”@+id/linearLayoutmain”
android:layout_alignStart=”@+id/scrollView” />
android:layout_width=”match_parent”
android:layout_height=”60dp”
android:gravity=”center”
android:text=”New Text6″
android:layout_below=”@+id/linearLayoutmain”
android:layout_alignStart=”@+id/scrollView” />
android:layout_width=”match_parent”
android:layout_height=”60dp”
android:gravity=”center”
android:text=”New Text7″
android:layout_below=”@+id/linearLayoutmain”
android:layout_alignStart=”@+id/scrollView” />
android:layout_width=”match_parent”
android:layout_height=”60dp”
android:gravity=”center”
android:text=”New Text9″
android:layout_below=”@+id/linearLayoutmain”
android:layout_alignStart=”@+id/scrollView” />
android:layout_width=”match_parent”
android:layout_height=”60dp”
android:gravity=”center”
android:text=”New Text10″
android:layout_below=”@+id/linearLayoutmain”
android:layout_alignStart=”@+id/scrollView” />
android:layout_width=”match_parent”
android:layout_height=”60dp”
android:gravity=”center”
android:text=”New Text11″
android:layout_below=”@+id/linearLayoutmain”
android:layout_alignStart=”@+id/scrollView” />

android:layout_width=”match_parent”
android:layout_height=”140dp”
android:layout_alignParentTop=”true”
android:layout_alignParentStart=”true”
android:id=”@+id/linearLayoutmain”>

android:orientation=”vertical”
android:layout_width=”match_parent”
android:background=”#000000″
android:layout_height=”80dp”
android:id=”@+id/part1″>

android:orientation=”vertical”
android:background=”@android:color/holo_red_light”
android:id=”@+id/part2″
android:layout_below=”@id/part1″
android:layout_width=”match_parent”
android:layout_height=”match_parent”>

android:layout_width=”40dp”
android:layout_height=”40dp”
android:text=”New Text”
android:id=”@+id/fab2″
android:layout_alignParentRight=”true”
android:background=”#ffffff”
android:layout_marginRight=”30dp”
android:layout_marginTop=”60dp” />

android:layout_width=”match_parent”
android:layout_height=”200dp”
android:id=”@+id/loading”>

android:layout_width=”40dp”
android:layout_height=”40dp”
android:text=”New Text”
android:id=”@+id/loading_text”
android:layout_centerVertical=”true”
android:layout_centerHorizontal=”true”
android:background=”#999″
android:layout_alignParentBottom=”true” />

android:layout_width=”40dp”
android:layout_height=”40dp”
android:text=”+”
android:id=”@+id/fab”
android:layout_alignParentBottom=”true”
android:layout_alignParentRight=”true”
android:layout_marginRight=”40dp”
android:layout_marginBottom=”40dp”
android:background=”#ccc” />

然后在MainActivity里加入如下代码 (已经写好注释)

public class MainActivity extends ActionBarActivity {
ScrollListen scroller; //自定义的ScrollView
RelativeLayout total; //title
RelativeLayout loading; //下拉布局的layout
TextView fab1;
TextView fab2;
TextView loadingtext; //下拉布局里的内容控件
int height; //title的高度
private int loading_height; //loading的高度

//Handler 用来处理下拉刷新的回缩
Handler a=new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if(msg.what==0x123){
ValueAnimator a=ValueAnimator.ofFloat(1,0);
a.setDuration(400);
a.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
showLoading((Float) valueAnimator.getAnimatedValue());
}
});
a.setInterpolator(new AccelerateDecelerateInterpolator());
a.start();
}
}
};
//loading动画传入一个percent表示动画进度
public void showLoading(float percnet){
loading.setTranslationY(-loading_height*(1-percnet));
loadingtext.setAlpha(percnet*255);
loadingtext.setRotation(360*percnet);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
scroller = (ScrollListen) findViewById(R.id.scrollView);
loading = (RelativeLayout) findViewById(R.id.loading);
loadingtext = (TextView) findViewById(R.id.loading_text);
fab1 = (TextView) findViewById(R.id.fab);
fab2 = (TextView) findViewById(R.id.fab2);
fab1.setTranslationY(1000); //将fab1移出屏幕
loading_height = dip2px(MainActivity.this,200); //因为我们的布局单位是dip但是这里的显示单位是px 所以高度我们都需要进行转换 转换方式见dip2px
height = dip2px(MainActivity.this,80);
loading.setTranslationY(-loading_height); //把loading部分上移至屏幕外
total=(RelativeLayout)findViewById(R.id.linearLayoutmain);
fab1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(MainActivity.this,”Create a new Message “,Toast.LENGTH_LONG).show();
}
});
scroller.setOnScrollListener(new ScrollListen.OnScrollListener() {
float scroll;
@Override
public void onOverScroll(float overScroll) {
float percent = overScroll/900;
if(percent>1) percent = 1;
showLoading(percent);
}
@Override
public void onOverScrollEnd() {
//开启一个获取数据的异步线程 并且在结束之后调用 a.sendEMptyMessage(0x123);
// 这里用一个延时代替
a.sendEmptyMessageDelayed(0x123,1000);
}
@Override
public void onScroll(int relativeScroll, int scrollY) {
if(scrollY>height) showFAB1();
else hideFAB1();
//控制回到顶部的fab的显示和隐藏
scroll -= relativeScroll;
if(scroll>0) scroll=0;
else if(scroll<-height) scroll=-height; total.setTranslationY(scroll); if(scroll==0) showFAB2(); else hideFAB2(); } }); } boolean isShown=false; private void showFAB1(){ if(!isShown) { FAB1Animator(1000, 0); isShown = true; } } private void hideFAB1(){ if(isShown) { FAB1Animator(0, 1000); isShown = false; } } boolean isShown2=true; private void showFAB2(){ if(!isShown2) { FAB2Animator(0, 1); isShown2 = true; } } private void hideFAB2(){ if(isShown2) { FAB2Animator(1, 0); isShown2 = false; } } private void FAB1Animator(int startValue,int endValue){ ValueAnimator v = ValueAnimator.ofInt(startValue,endValue); v.setDuration(300); v.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { fab1.setTranslationY(1.0f*(Integer) valueAnimator.getAnimatedValue()); } }); v.start(); } private void FAB2Animator(float startValue,float endValue){ ValueAnimator v = ValueAnimator.ofFloat(startValue,endValue); v.setDuration(300); v.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { fab2.setScaleX((Float) valueAnimator.getAnimatedValue()); fab2.setScaleY((Float) valueAnimator.getAnimatedValue()); } }); v.start(); } public static int dip2px(Context context, float dpValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (dpValue * scale + 0.5f); } }

这样仅用了300行左右的代码就实现了scroll控制整个界面动画,并且还加入了下拉刷新和fab等,整体效果不亚于一些知名应用的首页效果(无视素材),接下来只要替换控件背景就可以抽象成一个框架,接下来就只要向scrollview中的linearlayout里填充主要界面的内容就可以了。这么看来,只要充分掌握了复写和抽象的技巧,加上熟练的使用代码,要做出一个高效率有美观的界面是很简单的。
顺便一提,如果要向linearLayout里填充内容建议使用include标签再在onCreate里new一个ViewHandler 将控件的操作都写在里面,这样可以使代码看起来更有模块化的感觉同时可读性更强。同时,对于动画过程的描述最好写成0,1的float形式,再根据不同控件乘以不同常数得出数值,这样比较便于整体动画的处理。

今天的代码地址:https://github.com/SinoReimu/uiTest/
欢迎加星follow

分类: java/android技术

发表评论

电子邮件地址不会被公开。