源代码在这里:https://github.com/biricky/UCHome
源代码说明:
功能的实现在UCLinear.java文件中,返回键及home键的监听在MainActivity中。
忽略DragHelperCallBack及NewsSetting两个文件。
UI抽象
整体布局:将首页的布局抽象为五部分:
第一部分是被隐藏的新闻导航栏,暂时用TextView替代;
第二部分是搜索栏,用TextView替代;
第三部分为网站导航,由多个Button组成,仅画一个作为代表;
第四部分为新闻栏,使用ScrollView实现;
第五部分为底部导航,由多个Button横向线性排列;
源代码位置:res/layout/
类介绍
介绍具体实现之前,要介绍一下使用到的几个类。
两个最最最重要的类
ViewDragHelp
https://developer.android.com/reference/android/support/v4/widget/ViewDragHelper.html
直接把它的说明翻译过来(英语很渣...):
ViewDragHelper是一个用来编写用户自定义ViewGroups的实用类,它为用户提供了许多实用的方法,可以跟踪用户的拖拽动作,并可以重新定位子views在其父ViewGroup中的位置。
ViewDragHelp.Callback
http://developer.android.com/reference/android/support/v4/widget/ViewDragHelper.Callback.html
类的名字已经很明显了,就是ViewDragHelper的回调函数。
关于他的使用方法,这里给了一个非常好的总结:http://flavienlaurent.com/blog/2013/08/28/each-navigation-drawer-hides-a-viewdraghelper/
VelocityTracker
http://developer.android.com/reference/android/view/VelocityTracker.html
获取滑动速度的类。
UCLinear.java
package com.example.ucfirstpage;
import android.R.bool;
import android.R.integer;
import android.content.Context;
import android.support.v4.view.VelocityTrackerCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewTreeObserver.OnPreDrawListener;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.AnimationSet;
import android.view.animation.TranslateAnimation;
import android.widget.AdapterViewAnimator;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
public class UCLinear extends RelativeLayout{
private static int CONTENT_ORI_TOP_LOC;
private boolean isInRange = true;
public boolean isOrigin = true; //是否是初始状态,用于返回判断
private ViewDragHelper mDragHelper;
private DragHelperCallback mCallBack;
private VelocityTracker mVTracker;
private int mGuideHeight; //导航栏高度
private int mSearchHeight; //搜索栏高度
private int mWebGuideHeight; //网站导航高度
private int mContentHeight; //内容高度
private int mTotalHeight; //总高度
private View mViewGuide; //导航栏
private View mViewSearch; //搜索部分
private View mViewWebGuide; //网站导航
private View mViewContent; //内容
private View mViewBottom;//底部导航
//bottom中的五个button
private Button btnPrev,btnNext,btnMore,btnPage,btnToHome;
public UCLinear(Context context, AttributeSet attrs) {
super(context, attrs);
mCallBack = new DragHelperCallback();
mDragHelper = ViewDragHelper.create(this, mCallBack);
this.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
@Override
public boolean onPreDraw() {
CONTENT_ORI_TOP_LOC = mViewContent.getTop();
mGuideHeight = mViewGuide.getHeight();
mSearchHeight = mViewSearch.getHeight();
mWebGuideHeight = mViewWebGuide.getHeight();
mTotalHeight = UCLinear.this.getHeight();
mViewGuide.bringToFront();
mViewGuide.setTranslationY(-mGuideHeight);
UCLinear.this.getViewTreeObserver().removeOnPreDrawListener(this);
return false;
}
});
}
public void setBackToOrigin(){
if (!isOrigin){
isInRange = true;
isOrigin = true;
mViewSearch.setScaleX(1);
mViewSearch.setScaleY(1);
mViewWebGuide.setScaleX(1);
mViewWebGuide.setScaleY(1);
TranslateAnimation btnPrev_ta = new TranslateAnimation(0,-btnPrev.getTranslationX(), 0, 0);
btnPrev_ta.setDuration(100);
btnPrev_ta.setFillAfter(true);
btnPrev.startAnimation(btnPrev_ta);
btnPrev.setAlpha(1);
btnPrev_ta.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
btnPrev.setTranslationX(0);
btnPrev.clearAnimation();
}
});
TranslateAnimation btnNext_ta = new TranslateAnimation(0,-btnNext.getTranslationX(), 0, 0);
btnNext_ta.setDuration(100);
btnNext_ta.setFillAfter(true);
btnNext.startAnimation(btnNext_ta);
btnNext.setAlpha(1);
btnNext_ta.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
btnNext.setTranslationX(0);
btnNext.clearAnimation();
}
});
TranslateAnimation btnPage_ta = new TranslateAnimation(0,-btnPage.getTranslationX(), 0, 0);
btnPage_ta.setDuration(100);
btnPage_ta.setFillAfter(true);
btnPage.startAnimation(btnPage_ta);
btnPage.setAlpha(1);
btnPage_ta.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
btnPage.setTranslationX(0);
btnPage.clearAnimation();
}
});
TranslateAnimation btnToHome_ta = new TranslateAnimation(0, -btnToHome.getTranslationX(), 0, 0);
btnToHome_ta.setDuration(100);
btnToHome_ta.setFillAfter(true);
btnToHome.startAnimation(btnToHome_ta);
btnToHome_ta.setAnimationListener(new AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
btnToHome.setTranslationX(0);
btnToHome.clearAnimation();
}
});
btnMore.setAlpha(1);
mViewGuide.setTranslationY(-mGuideHeight);
mViewContent.setScrollY(-mViewContent.getScrollY());
requestLayout();
}
}
//计算拖动速度
@Override
public void computeScroll() {
if(mDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (mVTracker == null) {
mVTracker = VelocityTracker.obtain();
}
mVTracker.addMovement(ev);
final VelocityTracker vt = mVTracker;
if (ev.getAction() == MotionEvent.ACTION_MOVE) {
vt.computeCurrentVelocity(1000);
if ( vt.getYVelocity() < -1500 && isInRange) {
mDragHelper.smoothSlideViewTo(mViewContent, 0, mGuideHeight);
this.postInvalidate();
isInRange = false;
isOrigin = false;
return false;
}
else if (vt.getYVelocity() > 500 && mViewContent.getScrollY() == 0) {
this.setBackToOrigin();
return true;
}
}
return mDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mDragHelper.processTouchEvent(event);
return false;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
mViewGuide.layout(
0,
0,
mViewGuide.getMeasuredWidth(),
mViewGuide.getMeasuredHeight());
mViewSearch.layout(
0,
0,
mViewSearch.getMeasuredWidth(),
mViewSearch.getMeasuredHeight());
mViewWebGuide.layout(
0,
mViewSearch.getMeasuredHeight(),
mViewWebGuide.getMeasuredWidth(),
mViewSearch.getMeasuredHeight()+mViewWebGuide.getMeasuredHeight());
mViewBottom.layout(
0,
getMeasuredHeight()-mViewBottom.getMeasuredHeight(),
mViewBottom.getMeasuredWidth(),
getMeasuredHeight());
mViewContent.layout(
0,
mViewWebGuide.getMeasuredHeight()+mViewSearch.getMeasuredHeight(),
mViewContent.getMeasuredWidth(),
mViewWebGuide.getMeasuredHeight()+mViewSearch.getMeasuredHeight()+
getMeasuredHeight()-mViewGuide.getMeasuredHeight()-
mViewBottom.getMeasuredHeight());
}
public void initButton(){
btnPrev = (Button) findViewById(R.id.btnPrev);
btnNext = (Button) findViewById(R.id.btnNext);
btnMore = (Button) findViewById(R.id.btnMore);
btnPage = (Button) findViewById(R.id.btnPage);
btnToHome = (Button) findViewById(R.id.btnToHome);
}
@Override
protected void onFinishInflate() {
initButton();
mViewGuide= findViewById(R.id.uc_guide);
mViewSearch=findViewById(R.id.uc_search);
mViewWebGuide=findViewById(R.id.uc_webguide);
mViewContent=findViewById(R.id.uc_news);
mViewBottom=findViewById(R.id.uc_bottom);
}
class DragHelperCallback extends ViewDragHelper.Callback {
/**
* 设置mViewContent可拖拽
*/
@Override
public boolean tryCaptureView(View arg0, int arg1) {
if (mDragHelper.continueSettling(true))
return false;
return mViewContent == arg0 && isInRange;
}
/**
* 垂直拖拽的处理,这里对拖拽过程中mViewContent的移动进行处理
*/
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
int topBound = mGuideHeight;
int bottomBound = mViewWebGuide.getBottom();
int newTop = Math.min(Math.max(top, topBound), bottomBound);
return newTop;
}
/**
* 水平拖拽的处理
*/
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return super.clampViewPositionHorizontal(child, left, dx);
}
/**
* 水平可拖拽的距离范围
*/
@Override
public int getViewHorizontalDragRange(View child) {
return mViewContent.getWidth();
}
/**
* 垂直可拖拽的距离范围
*/
@Override
public int getViewVerticalDragRange(View child) {
return mTotalHeight;
}
/**
* 监听到View位置的变化,完成其他view动画的处理
*/
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
//获取导航栏相对手指移动的相对距离
float guidedy = dy / (float)(mSearchHeight+mWebGuideHeight-mGuideHeight) * mGuideHeight;
mViewGuide.setTranslationY(mViewGuide.getTranslationY() - guidedy);
//设置mViewSearch及mViewWebGuide缩放效果
float scale = (float) ((mViewWebGuide.getBottom()-mViewContent.getTop()) /
(float)(mSearchHeight+mWebGuideHeight-mGuideHeight));
mViewSearch.setPivotY(mViewSearch.getTop());
mViewSearch.setPivotX(getWidth()/2);
mViewSearch.setScaleY(1-scale/10);
mViewSearch.setScaleX(1-scale/10);
mViewWebGuide.setPivotY(mViewSearch.getTop());
mViewWebGuide.setPivotX(getWidth()/2);
mViewWebGuide.setScaleY(1-scale/10);
mViewWebGuide.setScaleX(1-scale/10);
//设置bottom中各button的效果
float alphady = (mViewWebGuide.getBottom() - mViewContent.getTop())/
(float)(mSearchHeight+mWebGuideHeight-mGuideHeight);
btnMore.setAlpha(1-alphady);
float homedy_tran = dy / (float)(mSearchHeight+mWebGuideHeight-mGuideHeight) *
(mViewBottom.getWidth()/2-btnToHome.getWidth()/2);
btnToHome.setTranslationX(btnToHome.getTranslationX() + homedy_tran);
float pagedy_tran = dy/(float)(mSearchHeight+mWebGuideHeight-mGuideHeight) *
(btnPage.getWidth()/2+btnMore.getWidth()/2);
btnPage.setTranslationX(btnPage.getTranslationX() + pagedy_tran);
btnPage.setAlpha(1-alphady);
float prevdy_tran = dy / (float)(mSearchHeight+mWebGuideHeight-mGuideHeight) *
(mViewBottom.getWidth()/2-btnPrev.getWidth()/2);
btnPrev.setTranslationX(btnPrev.getTranslationX() - prevdy_tran);
btnPrev.setAlpha(1-alphady);
float nextdy_tran = dy/(float)(mSearchHeight+mWebGuideHeight-mGuideHeight) *
(btnNext.getWidth()/2+btnMore.getWidth()/2);
btnNext.setTranslationX(btnNext.getTranslationX() - nextdy_tran);
btnNext.setAlpha(1-alphady);
}
/**
* 释放拖拽后执行,根据mViewContent的拖拽距离决定是否上滑或返回原位
*/
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
int movelen = CONTENT_ORI_TOP_LOC - mViewContent.getTop();
if (movelen > mWebGuideHeight){
isInRange = false;
isOrigin = false;
mDragHelper.settleCapturedViewAt(0, mGuideHeight);
postInvalidate();
}else {
mDragHelper.settleCapturedViewAt(0, CONTENT_ORI_TOP_LOC);
postInvalidate();
}
}
}
}
MainActivity.java实现
package com.example.ucfirstpage;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class MainActivity extends Activity {
public UCLinear ucView;
public Button btnHome;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ucView = (UCLinear)findViewById(R.id.uc_liner);
btnHome = (Button) findViewById(R.id.btnToHome);
btnHome.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (!ucView.isOrigin) {
ucView.setBackToOrigin();
}else {
}
}
});
}
@Override
public void onBackPressed() {
if (!ucView.isOrigin) {
ucView.setBackToOrigin();
}else {
super.onBackPressed();
}
}
}
总结
收获:1. 学习了ViewGroup、View以及事件分发机制;
2. Animation的使用更熟练;
3. 学习了ViewDragHelper;
4. Android UI的实现过程有了认识
5. 熟悉了Android的开发环境
不足:
1.下滑动画效果的处理不理想,这里需要解决ScrollView和ViewGroup动画冲突的问题,暂时想到的解决办法是覆盖ScrollView的onTouchevent()。
2.还需要继续扩展其他首页动画
3.经验太浅,对Android中的类了解的太少,导致实现的过程走了不少弯路。