`
v5qqbrowser
  • 浏览: 356109 次
文章分类
社区版块
存档分类
最新评论

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

 
阅读更多

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

照例先上运行效果图:




源代码下载地址: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();
		}
	}
在我们自定义ViewHorizontalMenuView中加入这个方法:

	/**
	 * 改变方向
	 */
	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




分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics