您的位置:首页 > 移动开发

酷狗app高仿系列

2015-08-22 12:05 471 查看
好。。。从今天开始写博客。

下了下酷狗发现很不错啊,交互流畅,ui美观,那就从它开刀。先来个高仿篇。。。。



下面是首页,画了框的就是感兴趣的地方v



好咱们先分析下他的结构,它的侧滑更qq差不多,就是多了个耳朵view在动,

还有就是在右边(我擦。。。是个人都知道的区别)。那么分析如果没有那个耳朵view再动是不是很简单啊,

用脚趾一想自定viewgroup,重写LinearLayout,重写HrizontalScrollview。。。。

是不是有很多方案。但是多了一个耳朵view LinearLayout就不太合适了。

因为不好布局是吧,重写viewGroup又太麻烦,那咱还是拿简单的HrizontalScrollview来开刀吧。

但是HrizontalScrollview可选的子view又是个要思考下的问题,

因为他不但有FrameLayout的功能还有灵活布置位置的功能。**

第一次使用markdown,有点生,擦 就放这里吧

<!--这是侧滑的horizontalScrollview-->
<com.example.kugou.view.MenuHorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/menu_horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
tools:context="com.example.kugou.MainActivity" >

<!--这是最外层的relativelayout ,为什么要自定义呢,博客等下会说-->
<com.example.kugou.view.MenuRelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#55ff0000" >

<!--会动的那个耳朵view-->
<ImageView
android:id="@id/main_menu_icon_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toRightOf="@id/main_content_id"
android:src="@drawable/ic_launcher" />

<!--内容区域-->
<RelativeLayout
android:id="@id/main_content_id"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ccc" >

</RelativeLayout>

<!--菜单区域-->
<LinearLayout
android:id="@id/main_menu_id"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_toRightOf="@id/main_content_id"
android:orientation="vertical" >

</LinearLayout>
</com.example.kugou.view.MenuRelativeLayout>

</com.example.kugou.view.MenuHorizontalScrollView>


这里有好几个id是定义在res下的因为这样方便侧滑控件的重复使用,

这个layout的作用 起到一个框架的作用,搭了一个架子,

相当于现在做房子做了个水泥柱,没有砌砖。

哦。。差点忘记了,id定义在res还有个作用是那个耳朵view是在relativelayout的第一个位置的,

如果直接写@+id的话编译器是会报错的。

好,该我们的侧滑控件MenuHorizontalScrollView登场了。。。。

首先来分析下这侧滑控件要做的事情:

1.能够滑动,且能够到一定位置时滚动到指定的位置,即menu的open和close。

(废话,侧滑没有这个功能还叫侧滑吗。。。)

2.要解决垂直滚动和水平滚动的冲突

暂时先是这两个问题

好,开始我们的coding

public class MenuHorizontalScrollView extends HorizontalScrollView {

/*这里设置menu为屏幕宽4/5*/
private final float menuScale = 4 / 5f;
private final int INVALID_POINTER = -1;
private int screenWidth;
private int menuViewWidth;
private int iconViewWidth;
private boolean isOpen;
/*拿到contentChild和menuChild  好做一些动画*/
private ViewGroup contentChild;
private ViewGroup menuChild;
private View iconView;
/*解决滑动冲突东东*/
private int mActivePointerId;
private float mInitialMotionX;
private float mInitialMotionY;

public MenuHorizontalScrollView(Context context) {
this(context, null);
// TODO Auto-generated constructor stub
}

public MenuHorizontalScrollView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
// TODO Auto-generated constructor stub
}

public MenuHorizontalScrollView(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
initView(context);
}

private void initView(Context context) {
// TODO Auto-generated method stub
screenWidth = context.getResources().getDisplayMetrics().widthPixels;
menuViewWidth = (int) (screenWidth * menuScale);
}

/**
* 因为我们的menu是固定大小的 是吧 所以我们要给他设置下
* 再测量之前设置下layoutParams是可以直接起作用的
* 因为在测量的时候来拿出layoutParams
* 相信玩过自定义view都都看到过 是吧
*
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub

ViewGroup rlChildview = (ViewGroup) getChildAt(0);
rlChildview.getLayoutParams().width = screenWidth + menuViewWidth;
contentChild = (ViewGroup) rlChildview
.findViewById(R.id.main_content_id);
contentChild.getLayoutParams().width = screenWidth;

menuChild = (ViewGroup) rlChildview.findViewById(R.id.main_menu_id);
menuChild.getLayoutParams().width = menuViewWidth;

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

contentChild.setPivotX(screenWidth);
contentChild.setPivotY(contentChild.getMeasuredHeight() / 2);

iconViewWidth = iconView.getMeasuredWidth();

}

/*这个方法就是在xmlParse后调用的 可以找到我们的一些控件*/
@Override
protected void onFinishInflate() {
// TODO Auto-generated method stub
super.onFinishInflate();

iconView = findViewById(R.id.main_menu_icon_id);
}

/* 解决垂直和水平滑动的问题*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
int action = ev.getAction() & MotionEventCompat.ACTION_MASK;

switch (action) {
case MotionEvent.ACTION_DOWN:
int index = MotionEventCompat.getActionIndex(ev);
mActivePointerId = MotionEventCompat.getPointerId(ev, index);
if (mActivePointerId == INVALID_POINTER) {
break;
}
mInitialMotionX = MotionEventCompat.getX(ev, index);
mInitialMotionY = MotionEventCompat.getY(ev, index);

break;
case MotionEvent.ACTION_MOVE:
int lastIndex = MotionEventCompat.findPointerIndex(ev,
mActivePointerId);
final float lastX = MotionEventCompat.getX(ev, lastIndex);
final float lastY = MotionEventCompat.getY(ev, lastIndex);
final float destX = Math.abs(lastX - mInitialMotionX);
final float destY = Math.abs(lastY - mInitialMotionY);
if (destX > destY) {
if (!isOpen && lastX - mInitialMotionX > 0) {
return false;
}
/*这里一定不能直接返回true  因为在父类要做一些判断和初始化变量不然没有效果了*/
return super.onInterceptTouchEvent(ev);
} else {
return false;
}
case MotionEvent.ACTION_UP:
break;
}

return super.onInterceptTouchEvent(ev);
}

/**
* 因为本身就是horizontalScrollview 就代用滚动
* 所以我们只需要判断一下actionup 是否要打开还是关闭menu
* @param ev
* @return
*/

@Override
public boolean onTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
if (ev.getAction() == MotionEvent.ACTION_UP) {
if (getScrollX() > menuViewWidth / 2) {
this.smoothScrollTo(menuViewWidth, 0);
isOpen = true;
} else {
this.smoothScrollTo(0, 0);
isOpen = false;
}
return true;
}
return super.onTouchEvent(ev);
}

/**
* 这里呢就是做一些动画了
*/

@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
// TODO Auto-generated method stub
super.onScrollChanged(l, t, oldl, oldt);

float scale = l * 1.0f / menuViewWidth;

float menuScale = 0.8f + 0.2f * scale;

float contentScale = 1.0f - 0.2f * scale;

/* menu change */
PropertyValuesHolder menuScaleX = PropertyValuesHolder.ofFloat(
"scaleX", menuScale);
PropertyValuesHolder menuScaleY = PropertyValuesHolder.ofFloat(
"scaleY", menuScale);
PropertyValuesHolder menuTranslateY = PropertyValuesHolder.ofFloat(
"translationX", -(1 - scale) * menuViewWidth * 0.2f);

ObjectAnimator menuOA = ObjectAnimator.ofPropertyValuesHolder(
menuChild, menuScaleX, menuScaleY, menuTranslateY);
menuOA.setDuration(0);
menuOA.start();

/* content change */

PropertyValuesHolder contentScaleX = PropertyValuesHolder.ofFloat(
"scaleX", contentScale);
PropertyValuesHolder contentScaleY = PropertyValuesHolder.ofFloat(
"scaleY", contentScale);
ObjectAnimator contentOA = ObjectAnimator.ofPropertyValuesHolder(
contentChild, contentScaleX, contentScaleY);
contentOA.setDuration(0);
contentOA.start();
/* menu icon change */
ObjectAnimator iconOA = ObjectAnimator.ofFloat(iconView,
"translationX", -scale * iconViewWidth * 0.5f);
iconOA.setDuration(0);
iconOA.start();
}

/*暂时给哪儿耳朵View使用,控制菜单的显示和隐藏*/
public void toggleMenu() {
if (isOpen) {
this.smoothScrollTo(0, 0);
} else {
this.smoothScrollTo(menuViewWidth, 0);
}
isOpen = !isOpen;
}

}


是不是很简单啊。就那么几十行代码。侧滑已经很多了。相信大家很容易明白。

不然拦截没有效果,大家可以去看下HorizontalScrollView的源码。

至于onScrollChanged方法里面的动画变化处理,

因为是模仿不是正规的开发,所以就直接使用了属性动画,

要是要兼容的话可以使用开源库nineoldandroids.

来看下效果:



是不是比较坑,完全不是我们想要的啊。该上的没上,该下的没下(到底是谁上谁下呢,,,,,我去 跑腿了。。。)
我们来分析下原因。返回到上面我们的布局文件,你会发现什么,用火眼看下,是不是不坑,是我们的代码写的坑是吧。前人挖坑后人填坑,前人种树后人乘凉,我们希望我们新进的公司的前辈都是种树的不是挖坑的,我们好乘凉喝茶看妹子,打豆豆啊,不然天天填坑,就不能愉快的下班找妹子约会了。。。嗨嗨。。中风了
所以我们要自定义RelativeLayout 是吧,前面已经说了。好开始我们的改造view


public MenuRelativeLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
setChildrenDrawingOrderEnabled(true);
}

@Override
protected int getChildDrawingOrder(int childCount, int i) {
// TODO Auto-generated method stub

return childCount - i - 1;
}


就两句代码句搞定了,改变下view的绘制顺序,好吧,这样我们的侧滑就完成了,哎。。。这天气是中暑的节奏。。先去喝杯水。

接下来呢是重头戏,上面的都是铺贴,房基是吧。

那么开始我们的内容区域吧。。。。

我们先来分析下哈



他不是两个activity而是在一个activity的一个容器里,是吧,

不然,下面的那个播放音乐的就不好搞了。

我们来思考下,这个怎么做好呢。。。

1.用一个FrameLayout作为容器,然后不断的添加view?

看来是个不错的选择,那么我们就先FrameLayout试试。

还是使用LayoutTransition呢?

3.滑动返回又怎么处理呢?直接自定义?还是v4包里的viewDragHelp?

进过试验和考虑我选择了使用FrameLayout作为容器

动画这一块使用预备容器的做法,因为“在addview是添加时自己加动画?

还是使用LayoutTransition呢”都不理想,在低端机下回闪一下体验不好。

滑动使用viewdraghelp可以减少很多代码量啊。

使用view作为activity一样的用,那么我们必须封装一下啊 是吧,

不然每次都一大堆重复代码,还能不能愉快的下班了。

定义一个接口,


public interface IViewPager {
View attachView(MenuContentFrameLayout rootView);

}


再来一个基类

public abstract class BaseViewPager implements IViewPager {

protected View contentView;
protected Context mContext;
protected MenuContentFrameLayout rootView;

public BaseViewPager(Context mContext, MenuContentFrameLayout rootView) {
// TODO Auto-generated constructor stub
this.mContext = mContext;
contentView = attachView(rootView);
this.rootView = rootView;

initView();
initValue();
setListener();
}

protected abstract void initView();

protected abstract void initValue();

protected abstract void setListener();

}


那首页view代码来分析下

public class MainPager extends BaseViewPager {

private Button goBtn;

public MainPager(Context context, MenuContentFrameLayout rootView) {
super(context, rootView);
// TODO Auto-generated constructor stub
}

@Override
public View attachView(MenuContentFrameLayout rootView) {
// TODO Auto-generated method stub
return LayoutInflater.from(mContext).inflate(
R.layout.main_content_layout, rootView, true);
}

@Override
protected void initView() {
// TODO Auto-generated method stub
goBtn = (Button) contentView.findViewById(R.id.btn_go);
}

@Override
protected void initValue() {
// TODO Auto-generated method stub

}

@Override
protected void setListener() {
// TODO Auto-generated method stub

goBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
new FriendListPager(mContext, rootView);
}
});

}

}这里写代码片


public class MainActivity extends Activity {
private MenuHorizontalScrollView menuHroizontalScrollView;
private ImageView menuIconView;
private MenuContentFrameLayout contentFl;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

initView();
initValue();
initListener();

}

private void initView() {
// TODO Auto-generated method stub
menuHroizontalScrollView = (MenuHorizontalScrollView) findViewById(R.id.menu_horizontal);
menuIconView = (ImageView) findViewById(R.id.main_menu_icon_id);
contentFl = (MenuContentFrameLayout) findViewById(R.id.content_fl);

}

private void initValue() {
// TODO Auto-generated method stub
new MainPager(this, contentFl);

}

private void initListener() {
// TODO Auto-generated method stub
menuIconView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
menuHroizontalScrollView.toggleMenu();
}
});
}
}


看到没有 我们的mainactivity使用new MainPager(this, contentFl);

就可以把mainViewpager添加到容器中了,

有点犯困,这里写的有点乱。最后附上所有代码,估计看大代码就情绪了,

最后上我们最核心的类

public class MenuContentFrameLayout extends FrameLayout {
/*重点滑动view*/
private ViewDragHelper mDragHelper;

private static final int MIN_FLING_VELOCITY = 400;

private int mActivePointerId;
private final int INVALID_POINTER = -1;
private float mInitialMotionX;
private float mInitialMotionY;

private DisplayMetrics displayMetrics;

/*保存添加的view*/
private ArrayList<View> stackViewList;

/*回收的view*/
private ArrayList<View> recycleViewList;

/*做动画的辅助view*/
private ContentSubFrameLayout subContentLayout;

public MenuContentFrameLayout(Context context) {
this(context, null);
// TODO Auto-generated constructor stub
}

public MenuContentFrameLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
// TODO Auto-generated constructor stub
}

public MenuContentFrameLayout(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub

initView(context);
}

private void initView(Context context) {
// TODO Auto-generated method stub
mDragHelper = ViewDragHelper.create(this, 1.0f, new MyCallback());
displayMetrics = context.getResources().getDisplayMetrics();
final float minVel = MIN_FLING_VELOCITY * displayMetrics.density + 0.5f;
mDragHelper.setMinVelocity(minVel);

stackViewList = new ArrayList<View>();
recycleViewList = new ArrayList<View>();

}

/**
* 这里处理滑动返回和滑动冲突处理
* @param ev
* @return
*/

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub

int action = ev.getAction() & MotionEventCompat.ACTION_MASK;

switch (action) {
case MotionEvent.ACTION_DOWN:
int index = MotionEventCompat.getActionIndex(ev);
mActivePointerId = MotionEventCompat.getPointerId(ev, index);
if (mActivePointerId == INVALID_POINTER) {
break;
}
mInitialMotionX = MotionEventCompat.getX(ev, index);
mInitialMotionY = MotionEventCompat.getY(ev, index);

break;
case MotionEvent.ACTION_MOVE:
int lastIndex = MotionEventCompat.findPointerIndex(ev,
mActivePointerId);
final float lastX = MotionEventCompat.getX(ev, lastIndex);
final float lastY = MotionEventCompat.getY(ev, lastIndex);
final float destX = Math.abs(lastX - mInitialMotionX);
final float destY = Math.abs(lastY - mInitialMotionY);
if (destX > destY) {
return mDragHelper.shouldInterceptTouchEvent(ev);
} else {
return false;
}
case MotionEvent.ACTION_UP:
break;
}

return mDragHelper.shouldInterceptTouchEvent(ev);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
mDragHelper.processTouchEvent(event);
return true;
}

@Override
public void computeScroll() {
// TODO Auto-generated method stub
if (mDragHelper.continueSettling(true)) {
invalidate();
}
}

/**
* v4包里的滑动回调
*/
private class MyCallback extends ViewDragHelper.Callback {

/*是否要捕获*/
@Override
public boolean tryCaptureView(View arg0, int arg1) {
// TODO Auto-generated method stub
return true;
}

/*水平滑动距离*/
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
// TODO Auto-generated method stub
if (getChildCount() == 1)
left -= dx * 2 / 3;
if (left < 0)
left = 0;
return left;
}

/*当手机松开时*/
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
// TODO Auto-generated method stub

if (getChildCount() == 1) {
mDragHelper.settleCapturedViewAt(0, releasedChild.getTop());
} else {
final float offset = releasedChild.getLeft() * 1.0f
/ releasedChild.getWidth();

mDragHelper.settleCapturedViewAt(xvel > 0 || xvel == 0
&& offset >= 0.5 ? releasedChild.getWidth() : 0,
releasedChild.getTop());
}
invalidate();
}

/*位置变化时,这里可以做动画*/
@Override
public void onViewPositionChanged(View changedView, int left, int top,
int dx, int dy) {
// TODO Auto-generated method stub
if (getChildCount() > 1) {

if (left >= changedView.getWidth()) {
removeView(changedView);
stackViewList.remove(stackViewList.size() - 1);

if (recycleViewList.size() > 0) {
addView(recycleViewList.get(recycleViewList.size() - 1),
0);
recycleViewList.remove(recycleViewList.size() - 1);

}
}
}
}

@Override
public int getViewHorizontalDragRange(View child) {
// TODO Auto-generated method stub
return child.getWidth();
}

}

@Override
public void addView(View child, android.view.ViewGroup.LayoutParams params) {
// TODO Auto-generated method stub
if (child instanceof ContentSubFrameLayout) {
super.addView(child, params);
} else {

if (stackViewList.size() == 0) {
super.addView(child, params);
} else {

if (stackViewList.size() >= 2) {
View oneChildView = getChildAt(0);
recycleViewList.add(oneChildView);
removeView(oneChildView);
}

/*添加一个辅助容器来作出现动画,避免闪一下问题*/
final ContentSubFrameLayout subContentLayout = new ContentSubFrameLayout(
getContext());
final FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT);
layoutParams.setMargins(displayMetrics.widthPixels, 0, 0, 0);
addView(subContentLayout, layoutParams);

subContentLayout.addView(child, params);

/*处理动画*/
ObjectAnimator subOA = ObjectAnimator.ofInt(subContentLayout,
"left", displayMetrics.widthPixels, 0);
subOA.setDuration(600);
subOA.start();

subOA.addListener(new AnimatorListenerAdapter() {

@Override
public void onAnimationEnd(Animator animation) {
// TODO Auto-generated method stub
final FrameLayout.LayoutParams layoutParams = (LayoutParams) subContentLayout
.getLayoutParams();
layoutParams.leftMargin = 0;
}
});
}
stackViewList.add(child);
}
}

}


代码里都有注释,最主要的是出现动画哪里,都加了一个辅助容器,

不然会出现闪一下的问题,自己可以试验下。

还加了一个缓存view,就是当view添加到多余三个时要把底部的view从容器移除,

不然侧滑菜单是会很卡顿,因为都看不到了,侧滑时还一直draw,也没有意思了,

是吧,所以就移除把,当要的时候再添加上去。界面的话没有去抠图,

程序员要的是核心代码和思想不是几个图片,是吧。

好了今天就到这里了。。。。困到不行啊,,眯会去。。。

哪里上传代码啊。。。晕

第一次写博客,哎,,第一次真是痛啊。后续持续更新,今天就先到这了

源代码
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: