您的位置:首页 > 编程语言 > Go语言

横向滑动ViewGoup(左边菜单右边内容)效果的实现

2012-10-16 16:49 495 查看
闲着无事,见到目前比较多的应用都用到了"左边菜单右边内容页"这样的形式展示数据,于是也着手写了一个。

照例先上运行效果图:











源代码下载地址:http://download.csdn.net/detail/shinay/4652739

下面是结构:



首先介绍HorizontalMenuView这个View,这是一个继承ViewGroup的View,也是最主要的一部分,由于这个类代码比较长,就只捡核心点的列出来。

HorizontalMenuView里有两个控件,都是由代码创建的,分别是一个ListView(用于放置菜单项)和一个LinearLayout(用于放置内容页)。

lv_menu = new ListView(context);
LayoutParams params = new LayoutParams(childWidths[0], LayoutParams.FILL_PARENT);
lv_menu.setLayoutParams(params);
lv_menu.setCacheColorHint(Color.TRANSPARENT);
lv_menu.setBackgroundColor(Color.WHITE);
lv_menu.setFocusable(false);
addView(lv_menu);

ll_content = new LinearLayout(context);
params = new LayoutParams(childWidths[1], LayoutParams.FILL_PARENT);
ll_content.setOrientation(LinearLayout.HORIZONTAL);
ll_content.setLayoutParams(params);
ll_content.setBackgroundColor(Color.GRAY);
addView(ll_content);


至于宽度是根据屏幕的宽度所设置的。

另外,必须实现onLayout()和onMeasure(),否则这个View将无法正常显示。这两个方法用于计算子View的宽高度及所画的位置。

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childLeft = 0;
int childCount = getChildCount();

for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
if (childView.getVisibility() != View.GONE) {
int childWidth = childWidths[i];
childView.layout(childLeft, 0, childLeft + childWidth,
childView.getMeasuredHeight());
childLeft += childWidth;
}
}
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int count = getChildCount();
for (int i = 0; i < count; i++) {
getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
}
}


实现了显示之后,就要令view能够滚动了,主要是实现onTouchEvent()和computeScrollI()方法,当然,还需要一个scroller对象。

@Override
public boolean onTouchEvent(MotionEvent event) {
// 如果这个方法return true, 那么MotionEvent事件将不会往下传递
// 如果这个方法return false, 那么MotionEvent事件将会往下传递
int action = event.getAction();
float x = event.getX();

switch (action) {
case MotionEvent.ACTION_DOWN:
if (velocityTracker == null) {
velocityTracker = VelocityTracker.obtain();
velocityTracker.addMovement(event);
}
if (!scroller.isFinished()) {
scroller.abortAnimation();
}
mLastMotionX = x;
break;
case MotionEvent.ACTION_MOVE:
int deltaX = (int) (mLastMotionX - x);
if (isCanMove(deltaX)) {
if (velocityTracker != null) {
velocityTracker.addMovement(event);
}
scrollBy(deltaX, 0);
}

// 越界判断
if (getScrollX() < 0) {
scrollTo(0, 0);
}
if (getScrollX() > childWidths[0]) {
scrollTo(childWidths[0], 0);
}

mLastMotionX = x;
break;
case MotionEvent.ACTION_UP:
int velocityX = 0;
if (velocityTracker != null) {
velocityTracker.addMovement(event);
velocityTracker.computeCurrentVelocity(1000);
velocityX = (int) velocityTracker.getXVelocity();
}
if (velocityX > SNAP_VELOCITY) {
snapToScreen(MENU_PAGE);
} else if (velocityX < -SNAP_VELOCITY) {
snapToScreen(CONTENT_PAGE);
} else {
snapToDestination();
}

if (velocityTracker != null) {
velocityTracker.recycle();
velocityTracker = null;
}
break;
}

return true;
}


@Override
public void computeScroll() {
if (scroller.computeScrollOffset()) {
scrollTo(scroller.getCurrX(), scroller.getCurrY());
postInvalidate();
}
}


其中,snapToScreen()方法就实现了滚动动画,从而在手指抬起的时候,根据判断会滚动到相应的位置。

/**
* 跳到指定页
* @param whichScreen
*/
private void snapToScreen(int whichScreen) {
if ((whichScreen == MENU_PAGE && getScrollX() != 0)
|| (whichScreen == CONTENT_PAGE && getScrollX() != childWidths[0])) {
int delta = 0;
if (whichScreen == MENU_PAGE) {
delta = 0 - getScrollX();
} else {
delta = childWidths[0] - getScrollX();
}
scroller.startScroll(getScrollX(), 0, delta, 0, Math.abs(delta) * 2);
currentPage = whichScreen;
invalidate();
}
}


这样,大概就实现一大部分了,然后就是当Menu的ListView点击时,打开相应的内容页。

lv_menu.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
menu_selected = position;

for (int i = 0, count = parent.getChildCount(); i < count; i++) {
int textColor = Color.GRAY;
if (menu_selected == i) {
textColor = Color.DKGRAY;
}
((TextView) parent.getChildAt(i)).setTextColor(textColor);
}

openContentPage();
snapToScreen(CONTENT_PAGE);
}
});


/**
* 打开内容页
*/
private void openContentPage() {
if (menuData != null) {
Intent intent = menuData.get(menu_selected).getIntent();
if (intent != null && context instanceof ActivityGroup) {
ll_content.removeAllViews();
destroyActivityFromGroup(context, "content");
Window contentActivity = ((ActivityGroup) context)
.getLocalActivityManager().startActivity("content",
intent);
LayoutParams params = new LayoutParams(
LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
ll_content.addView(contentActivity.getDecorView(), params);
invalidate();
}
}
}
这里注意下,由于要加入内容页,内容页为Activity的View,所以这个View需要在ActivityGroup中使用。

接着我们的ActivityGroup就可以使用这个自定义的View了:
package com.lxb.horizontalmenu;

import java.util.ArrayList;
import java.util.List;

import android.app.ActivityGroup;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.ViewGroup.LayoutParams;

import com.lxb.horizontalmenu.testActivity.Activity1;
import com.lxb.horizontalmenu.testActivity.Activity2;
import com.lxb.horizontalmenu.testActivity.Activity3;
import com.lxb.horizontalmenu.testActivity.Activity4;

public class HorizontalMenuActivity extends ActivityGroup {

private HorizontalMenuView horizontalMenuView;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

DisplayMetrics metric = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metric);
int width = metric.widthPixels; // 屏幕宽度(像素)
int height = metric.heightPixels; // 屏幕高度(像素)

List<MenuItem> menuItem = new ArrayList<MenuItem>();
menuItem.add(new MenuItem("菜单1", new Intent(this, Activity1.class)));
menuItem.add(new MenuItem("菜单2", new Intent(this, Activity2.class)));
menuItem.add(new MenuItem("菜单3", new Intent(this, Activity3.class)));
menuItem.add(new MenuItem("菜单4", new Intent(this, Activity4.class)));
menuItem.add(new MenuItem("菜单5", null));
menuItem.add(new MenuItem("菜单6", null));
menuItem.add(new MenuItem("菜单7", null));
menuItem.add(new MenuItem("菜单8", null));
menuItem.add(new MenuItem("菜单9", null));
menuItem.add(new MenuItem("菜单10", null));
menuItem.add(new MenuItem("菜单11", null));
menuItem.add(new MenuItem("菜单12", null));
menuItem.add(new MenuItem("菜单13", null));
menuItem.add(new MenuItem("菜单14", null));
menuItem.add(new MenuItem("菜单15", null));
menuItem.add(new MenuItem("菜单16", null));
menuItem.add(new MenuItem("菜单17", null));
menuItem.add(new MenuItem("菜单18", null));
menuItem.add(new MenuItem("菜单19", null));
menuItem.add(new MenuItem("菜单20", null));

horizontalMenuView = new HorizontalMenuView(this, width, height, menuItem);
horizontalMenuView.setLayoutParams(new LayoutParams(
LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));

setContentView(horizontalMenuView);
}

}
MenuItem内容如下:

package com.lxb.horizontalmenu;

import android.content.Intent;

public class MenuItem {

private String title; // 菜单项的标题
private Intent intent; // 菜单项的Intent

public MenuItem(String title, Intent intent) {
this.title = title;
this.intent = intent;
}

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}

public Intent getIntent() {
return intent;
}

public void setIntent(Intent intent) {
this.intent = intent;
}

}


以上就是最初我完成的效果,但是后来发现还是有很多地方是不完善的:

1. 内容页是ListView时,ListView无法滑动了。

2. 当屏幕方向改变时,View被重画或者没被重画但是宽度显示不正确。

于是又加入了一些处理:

1. 这点是由于ViewGroup的onTouch事件问题,内容页有ListView,但是由于它的onTouch无法获取到,一直被我们的自定义View控制着的原因。

这里用到onInterceptTouchEvent()与onTouchEvent(),onInterceptTouchEvent()会比onTouchEvent()先调用,我们这里起拦截作用,还有其返回值的问题,如果这个方法return true,那么MotionEvent事件将不会往下传递,反之则会向下传递。

因些我们只需要在onInterceptTouchEvent()方法中判断下我们的操作是倾向左右滑动还是上下滑动,如果是上下滑动,则把MotionEvent传去给listView处理,如果是左右滑动则还是留给自己处理。

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// 如果这个方法return true, 那么MotionEvent事件将不会往下传递
// 如果这个方法return false, 那么MotionEvent事件将会往下传递
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
if (velocityTracker == null) {
velocityTracker = VelocityTracker.obtain();
velocityTracker.addMovement(ev);
}
if (!scroller.isFinished()) {
scroller.abortAnimation();
}
lastInterceptX = ev.getX();
lastInterceptY = ev.getY();
mLastMotionX = ev.getX();
deliver = false;
break;
case MotionEvent.ACTION_MOVE:
float x = ev.getX();
float y = ev.getY();

float dx = x - lastInterceptX;
float dy = y - lastInterceptY;

if (Math.abs(dx) - Math.abs(dy) > 0 && Math.abs(dx) > 5) {
deliver = true;
} else {
deliver = false;

if (velocityTracker != null) {
velocityTracker.recycle();
velocityTracker = null;
}
}
case MotionEvent.ACTION_UP:
lastInterceptX = 0;
lastInterceptY = 0;
}

return deliver;
}


2. 这个处理问题处理的话,首先要让ActivityGroup在屏幕方向转换的时候不会重新new,也就是我们在注册Activity时加入

android:configChanges="orientation|keyboardHidden|navigation"


在ActivityGroup中实现onConfigurationChanged()

@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);

if (horizontalMenuView != null) {
horizontalMenuView.changeOrientation();
}
}
在我们自定义View HorizontalMenuView中加入这个方法:

/**
* 改变方向
*/
public void changeOrientation() {
int temp = screenWidth;
screenWidth = screenHeight;
screenHeight = temp;

childWidths[0] = screenWidth / 3 + 50;
childWidths[1] = screenWidth;

snapToScreen(currentPage);
}


OK, 大功告成!!

最后说明下,这里介绍的是我写这个Demo时候的思路,以及一些核心,有点乱,被菜鸟误导请勿怪罪。

源代码下载地址:http://download.csdn.net/detail/shinay/4652739
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: