横向滑动ViewGoup(左边菜单右边内容)效果的实现
2012-10-16 16:49
495 查看
闲着无事,见到目前比较多的应用都用到了"左边菜单右边内容页"这样的形式展示数据,于是也着手写了一个。
照例先上运行效果图:
源代码下载地址:http://download.csdn.net/detail/shinay/4652739
下面是结构:
首先介绍HorizontalMenuView这个View,这是一个继承ViewGroup的View,也是最主要的一部分,由于这个类代码比较长,就只捡核心点的列出来。
HorizontalMenuView里有两个控件,都是由代码创建的,分别是一个ListView(用于放置菜单项)和一个LinearLayout(用于放置内容页)。
至于宽度是根据屏幕的宽度所设置的。
另外,必须实现onLayout()和onMeasure(),否则这个View将无法正常显示。这两个方法用于计算子View的宽高度及所画的位置。
实现了显示之后,就要令view能够滚动了,主要是实现onTouchEvent()和computeScrollI()方法,当然,还需要一个scroller对象。
其中,snapToScreen()方法就实现了滚动动画,从而在手指抬起的时候,根据判断会滚动到相应的位置。
这样,大概就实现一大部分了,然后就是当Menu的ListView点击时,打开相应的内容页。
接着我们的ActivityGroup就可以使用这个自定义的View了:
以上就是最初我完成的效果,但是后来发现还是有很多地方是不完善的:
1. 内容页是ListView时,ListView无法滑动了。
2. 当屏幕方向改变时,View被重画或者没被重画但是宽度显示不正确。
于是又加入了一些处理:
1. 这点是由于ViewGroup的onTouch事件问题,内容页有ListView,但是由于它的onTouch无法获取到,一直被我们的自定义View控制着的原因。
这里用到onInterceptTouchEvent()与onTouchEvent(),onInterceptTouchEvent()会比onTouchEvent()先调用,我们这里起拦截作用,还有其返回值的问题,如果这个方法return true,那么MotionEvent事件将不会往下传递,反之则会向下传递。
因些我们只需要在onInterceptTouchEvent()方法中判断下我们的操作是倾向左右滑动还是上下滑动,如果是上下滑动,则把MotionEvent传去给listView处理,如果是左右滑动则还是留给自己处理。
2. 这个处理问题处理的话,首先要让ActivityGroup在屏幕方向转换的时候不会重新new,也就是我们在注册Activity时加入
在ActivityGroup中实现onConfigurationChanged()
OK, 大功告成!!
最后说明下,这里介绍的是我写这个Demo时候的思路,以及一些核心,有点乱,被菜鸟误导请勿怪罪。
源代码下载地址:http://download.csdn.net/detail/shinay/4652739
照例先上运行效果图:
源代码下载地址: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
相关文章推荐
- 基于Ajax+div的“左边菜单、右边内容”页面效果实现
- 基于Ajax+div的“左边菜单、右边内容”页面效果实现
- 基于Ajax+div的“左边菜单、右边内容”页面效果实现
- ViewPager 实现标题栏和内容对应滑动效果
- JS实现滑动菜单效果代码(包括Tab,选项卡,横向等效果)
- 使用ViewPager+GridView实现横向滑动的效果(一)
- 使用ViewPager+GridView实现横向滑动的效果(一)
- 使用ViewPager+GridView实现横向滑动的效果(一)
- 使用ViewPager+GridView实现横向滑动的效果(二)
- 实现滑动菜单效果DrawerLayout+NavigationView
- Android ViewPager内容部分随手势上下滑动隐藏与显示Indicator效果的实现
- 实现点击左边菜单,然后右边弹出网页内容
- iframe异步加载实现点击左边菜单加载右边内容实例讲解
- 通过自定义ViewGroup来实现侧滑菜单效果,解决滑动冲突
- cocos2D中实现滑动菜单CCScrollView+CCMenu效果,(注意不是cocos2D-x)!!
- jquery实现仿Flash的横向滑动菜单效果代码
- 通过CardView和RecyclerView实现横向卡片式滑动效果
- TabIndicator+ViewPager实现左右滑动菜单效果
- ViewPager+Fragment 滑动菜单效果 实现步骤
- 使用ViewPager和GridView配合,实现GridView横向水平滑动的效果。