横向滑动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();
		}
	}
在我们自定义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




  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
### 回答1: PC端横向滑动效果切换菜单是指在计算机上使用鼠标或键盘进行操作时,通过横向滑动的方式切换不同的菜单页面。 横向滑动效果切换菜单可以增加用户界面的互动性和美观性,让用户在浏览网页时更加方便快捷地切换不同菜单。通常情况下,使用横向滑动功能切换菜单需要满足以下几个条件: 1. 导航菜单支持横向排列:为了实现横向滑动切换菜单菜单栏需要支持横向排列,这样用户才能够通过滑动手势移动到不同的菜单选项上。 2. 滑动功能的实现:使用横向滑动切换菜单时,需要通过鼠标或键盘等设备的操作来模拟滑动手势。例如,可以使用鼠标的拖拽或滚动功能,或者使用键盘的左右箭头键来实现滑动切换。 3. 切换菜单页面的动画效果:为了增加用户体验,切换菜单时可以添加动画效果,让切换过程更加平滑和自然,使用户感知到页面的切换。 4. 菜单标识或导航指示:为了让用户知道当前所处的菜单位置,可以在菜单栏添加标识或导航指示,如高亮显示当前选中的菜单项,或在页面上方显示当前菜单位置的标题等。 总的来说,PC端横向滑动效果切换菜单通过横向排列菜单项并利用滑动手势来切换不同的菜单页面,可以提升用户的界面交互性和使用体验。这种切换方式在许多网页设计中经常使用,使用户能够更加方便地浏览不同的内容和功能模块。 ### 回答2: PC端横向滑动效果是一种常见的切换菜单的方式,它可以提供更流畅、直观的用户体验。在实现横向滑动效果切换菜单的过程中,我们可以采用以下几个步骤: 1. 设计页面结构:首先,我们需要根据需求设计页面的基本结构,确定好菜单的布局方式和样式。可以使用HTML和CSS进行布局,并使用JavaScript来实现交互效果。 2. 添加触摸和滑动事件监听器:在JavaScript代码中,我们可以添加事件监听器,用于监控用户的触摸和滑动行为。通过监听用户的手势动作,我们可以捕获到用户的滑动方向和距离。 3. 根据滑动方向进行切换:根据捕获到的滑动方向和距离,我们可以根据需求决定如何切换菜单。例如,如果用户向左滑动一定距离,我们可以切换到下一个菜单;如果用户向右滑动一定距离,我们可以切换到上一个菜单。 4. 添加滑动动画效果:为了给用户更好的视觉体验,我们可以在切换菜单时添加一些滑动动画效果。通过改变菜单的位置和透明度等属性,可以实现平滑的切换效果。 5. 响应式设计:在实现PC端横向滑动效果切换菜单时,还需要考虑到响应式设计的问题。我们可以通过媒体查询和适当的CSS样式调整,使菜单在不同的屏幕尺寸下有更好的适配效果。 总的来说,PC端横向滑动效果切换菜单可以通过监听用户的滑动行为,结合动画效果实现平滑的菜单切换。这样可以提升用户的交互体验,使网页更加吸引人。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值