横向滑动ViewGoup(左边菜单右边内容)效果的实现
发布日期:2021-09-27 14:12:39 浏览次数:3 分类:技术文章

本文共 9506 字,大约阅读时间需要 31 分钟。

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

照例先上运行效果图:

 

源代码下载地址:

 

 

下面是结构:

 

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

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

 

[java]
  1. lv_menu = new ListView(context);  
  2. LayoutParams params = new LayoutParams(childWidths[0], LayoutParams.FILL_PARENT);  
  3. lv_menu.setLayoutParams(params);  
  4. lv_menu.setCacheColorHint(Color.TRANSPARENT);  
  5. lv_menu.setBackgroundColor(Color.WHITE);  
  6. lv_menu.setFocusable(false);  
  7. addView(lv_menu);  
  8.   
  9. ll_content = new LinearLayout(context);  
  10. params = new LayoutParams(childWidths[1], LayoutParams.FILL_PARENT);  
  11. ll_content.setOrientation(LinearLayout.HORIZONTAL);  
  12. ll_content.setLayoutParams(params);  
  13. ll_content.setBackgroundColor(Color.GRAY);  
  14. addView(ll_content);  
 

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

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

 

[java]
  1. @Override  
  2. protected void onLayout(boolean changed, int l, int t, int r, int b) {  
  3.     int childLeft = 0;  
  4.     int childCount = getChildCount();  
  5.   
  6.     for (int i = 0; i < childCount; i++) {  
  7.         View childView = getChildAt(i);  
  8.         if (childView.getVisibility() != View.GONE) {  
  9.             int childWidth = childWidths[i];  
  10.             childView.layout(childLeft, 0, childLeft + childWidth,  
  11.                     childView.getMeasuredHeight());  
  12.             childLeft += childWidth;  
  13.         }  
  14.     }  
  15. }  
  16.   
  17. @Override  
  18. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  19.     super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  20.     int count = getChildCount();  
  21.     for (int i = 0; i < count; i++) {  
  22.         getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);  
  23.     }  
  24. }  
 

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

 

 

[java]
  1. @Override  
  2. public boolean onTouchEvent(MotionEvent event) {  
  3.     // 如果这个方法return true, 那么MotionEvent事件将不会往下传递  
  4.     // 如果这个方法return false, 那么MotionEvent事件将会往下传递  
  5.     int action = event.getAction();  
  6.     float x = event.getX();  
  7.   
  8.     switch (action) {  
  9.     case MotionEvent.ACTION_DOWN:  
  10.         if (velocityTracker == null) {  
  11.             velocityTracker = VelocityTracker.obtain();  
  12.             velocityTracker.addMovement(event);  
  13.         }  
  14.         if (!scroller.isFinished()) {  
  15.             scroller.abortAnimation();  
  16.         }  
  17.         mLastMotionX = x;  
  18.         break;  
  19.     case MotionEvent.ACTION_MOVE:  
  20.         int deltaX = (int) (mLastMotionX - x);  
  21.         if (isCanMove(deltaX)) {  
  22.             if (velocityTracker != null) {  
  23.                 velocityTracker.addMovement(event);  
  24.             }  
  25.             scrollBy(deltaX, 0);  
  26.         }  
  27.   
  28.         // 越界判断  
  29.         if (getScrollX() < 0) {  
  30.             scrollTo(0, 0);  
  31.         }  
  32.         if (getScrollX() > childWidths[0]) {  
  33.             scrollTo(childWidths[0], 0);  
  34.         }  
  35.   
  36.         mLastMotionX = x;  
  37.         break;  
  38.     case MotionEvent.ACTION_UP:  
  39.         int velocityX = 0;  
  40.         if (velocityTracker != null) {  
  41.             velocityTracker.addMovement(event);  
  42.             velocityTracker.computeCurrentVelocity(1000);  
  43.             velocityX = (int) velocityTracker.getXVelocity();  
  44.         }  
  45.         if (velocityX > SNAP_VELOCITY) {  
  46.             snapToScreen(MENU_PAGE);  
  47.         } else if (velocityX < -SNAP_VELOCITY) {  
  48.             snapToScreen(CONTENT_PAGE);  
  49.         } else {  
  50.             snapToDestination();  
  51.         }  
  52.   
  53.         if (velocityTracker != null) {  
  54.             velocityTracker.recycle();  
  55.             velocityTracker = null;  
  56.         }  
  57.         break;  
  58.     }  
  59.   
  60.     return true;  
  61. }  
 
[java]
  1. @Override  
  2. public void computeScroll() {  
  3.     if (scroller.computeScrollOffset()) {  
  4.         scrollTo(scroller.getCurrX(), scroller.getCurrY());  
  5.         postInvalidate();  
  6.     }  
  7. }  
 

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

 

 

[java]
  1. /** 
  2.  * 跳到指定页 
  3.  * @param whichScreen 
  4.  */  
  5. private void snapToScreen(int whichScreen) {  
  6.     if ((whichScreen == MENU_PAGE && getScrollX() != 0)  
  7.             || (whichScreen == CONTENT_PAGE && getScrollX() != childWidths[0])) {  
  8.         int delta = 0;  
  9.         if (whichScreen == MENU_PAGE) {  
  10.             delta = 0 - getScrollX();  
  11.         } else {  
  12.             delta = childWidths[0] - getScrollX();  
  13.         }  
  14.         scroller.startScroll(getScrollX(), 0, delta, 0, Math.abs(delta) * 2);  
  15.         currentPage = whichScreen;  
  16.         invalidate();  
  17.     }  
  18. }  
  

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

[java]
  1. lv_menu.setOnItemClickListener(new OnItemClickListener() {  
  2.     @Override  
  3.     public void onItemClick(AdapterView<?> parent, View view,  
  4.             int position, long id) {  
  5.         menu_selected = position;  
  6.           
  7.         for (int i = 0, count = parent.getChildCount(); i < count; i++) {  
  8.             int textColor = Color.GRAY;  
  9.             if (menu_selected == i) {  
  10.                 textColor = Color.DKGRAY;  
  11.             }  
  12.             ((TextView) parent.getChildAt(i)).setTextColor(textColor);  
  13.         }  
  14.   
  15.         openContentPage();  
  16.         snapToScreen(CONTENT_PAGE);  
  17.     }  
  18. });  
 
[java]
  1. /** 
  2.  * 打开内容页 
  3.  */  
  4. private void openContentPage() {  
  5.     if (menuData != null) {  
  6.         Intent intent = menuData.get(menu_selected).getIntent();  
  7.         if (intent != null && context instanceof ActivityGroup) {  
  8.             ll_content.removeAllViews();  
  9.             destroyActivityFromGroup(context, "content");  
  10.             Window contentActivity = ((ActivityGroup) context)  
  11.                     .getLocalActivityManager().startActivity("content",  
  12.                             intent);  
  13.             LayoutParams params = new LayoutParams(  
  14.                     LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);  
  15.             ll_content.addView(contentActivity.getDecorView(), params);  
  16.             invalidate();  
  17.         }  
  18.     }  
  19. }  
 

这里注意下,由于要加入内容页,内容页为Activity的View,所以这个View需要在ActivityGroup中使用。

 

接着我们的ActivityGroup就可以使用这个自定义的View了:

[java]
  1. package com.lxb.horizontalmenu;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.List;  
  5.   
  6. import android.app.ActivityGroup;  
  7. import android.content.Intent;  
  8. import android.content.res.Configuration;  
  9. import android.os.Bundle;  
  10. import android.util.DisplayMetrics;  
  11. import android.view.ViewGroup.LayoutParams;  
  12.   
  13. import com.lxb.horizontalmenu.testActivity.Activity1;  
  14. import com.lxb.horizontalmenu.testActivity.Activity2;  
  15. import com.lxb.horizontalmenu.testActivity.Activity3;  
  16. import com.lxb.horizontalmenu.testActivity.Activity4;  
  17.   
  18. public class HorizontalMenuActivity extends ActivityGroup {  
  19.   
  20.     private HorizontalMenuView horizontalMenuView;  
  21.   
  22.     @Override  
  23.     public void onCreate(Bundle savedInstanceState) {  
  24.         super.onCreate(savedInstanceState);  
  25.   
  26.         DisplayMetrics metric = new DisplayMetrics();  
  27.         getWindowManager().getDefaultDisplay().getMetrics(metric);  
  28.         int width = metric.widthPixels; // 屏幕宽度(像素)  
  29.         int height = metric.heightPixels; // 屏幕高度(像素)  
  30.   
  31.         List<MenuItem> menuItem = new ArrayList<MenuItem>();  
  32.         menuItem.add(new MenuItem("菜单1", new Intent(this, Activity1.class)));  
  33.         menuItem.add(new MenuItem("菜单2", new Intent(this, Activity2.class)));  
  34.         menuItem.add(new MenuItem("菜单3", new Intent(this, Activity3.class)));  
  35.         menuItem.add(new MenuItem("菜单4", new Intent(this, Activity4.class)));  
  36.         menuItem.add(new MenuItem("菜单5", null));  
  37.         menuItem.add(new MenuItem("菜单6", null));  
  38.         menuItem.add(new MenuItem("菜单7", null));  
  39.         menuItem.add(new MenuItem("菜单8", null));  
  40.         menuItem.add(new MenuItem("菜单9", null));  
  41.         menuItem.add(new MenuItem("菜单10", null));  
  42.         menuItem.add(new MenuItem("菜单11", null));  
  43.         menuItem.add(new MenuItem("菜单12", null));  
  44.         menuItem.add(new MenuItem("菜单13", null));  
  45.         menuItem.add(new MenuItem("菜单14", null));  
  46.         menuItem.add(new MenuItem("菜单15", null));  
  47.         menuItem.add(new MenuItem("菜单16", null));  
  48.         menuItem.add(new MenuItem("菜单17", null));  
  49.         menuItem.add(new MenuItem("菜单18", null));  
  50.         menuItem.add(new MenuItem("菜单19", null));  
  51.         menuItem.add(new MenuItem("菜单20", null));  
  52.   
  53.         horizontalMenuView = new HorizontalMenuView(this, width, height, menuItem);  
  54.         horizontalMenuView.setLayoutParams(new LayoutParams(  
  55.                 LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));  
  56.   
  57.         setContentView(horizontalMenuView);  
  58.     }  
  59.   
  60. }  
 

MenuItem内容如下:

 

[java]
  1. package com.lxb.horizontalmenu;  
  2.   
  3. import android.content.Intent;  
  4.   
  5. public class MenuItem {  
  6.   
  7.     private String title; // 菜单项的标题  
  8.     private Intent intent; // 菜单项的Intent  
  9.   
  10.     public MenuItem(String title, Intent intent) {  
  11.         this.title = title;  
  12.         this.intent = intent;  
  13.     }  
  14.   
  15.     public String getTitle() {  
  16.         return title;  
  17.     }  
  18.   
  19.     public void setTitle(String title) {  
  20.         this.title = title;  
  21.     }  
  22.   
  23.     public Intent getIntent() {  
  24.         return intent;  
  25.     }  
  26.   
  27.     public void setIntent(Intent intent) {  
  28.         this.intent = intent;  
  29.     }  
  30.   
  31. }  
 

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

 

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

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

 

于是又加入了一些处理:

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

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

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

[java]
  1. @Override  
  2. public boolean onInterceptTouchEvent(MotionEvent ev) {  
  3.     // 如果这个方法return true, 那么MotionEvent事件将不会往下传递  
  4.     // 如果这个方法return false, 那么MotionEvent事件将会往下传递  
  5.     int action = ev.getAction();  
  6.     switch (action) {  
  7.     case MotionEvent.ACTION_DOWN:  
  8.         if (velocityTracker == null) {  
  9.             velocityTracker = VelocityTracker.obtain();  
  10.             velocityTracker.addMovement(ev);  
  11.         }  
  12.         if (!scroller.isFinished()) {  
  13.             scroller.abortAnimation();  
  14.         }  
  15.         lastInterceptX = ev.getX();  
  16.         lastInterceptY = ev.getY();  
  17.         mLastMotionX = ev.getX();  
  18.         deliver = false;  
  19.         break;  
  20.     case MotionEvent.ACTION_MOVE:  
  21.         float x = ev.getX();  
  22.         float y = ev.getY();  
  23.   
  24.         float dx = x - lastInterceptX;  
  25.         float dy = y - lastInterceptY;  
  26.   
  27.         if (Math.abs(dx) - Math.abs(dy) > 0 && Math.abs(dx) > 5) {  
  28.             deliver = true;  
  29.         } else {  
  30.             deliver = false;  
  31.   
  32.             if (velocityTracker != null) {  
  33.                 velocityTracker.recycle();  
  34.                 velocityTracker = null;  
  35.             }  
  36.         }  
  37.     case MotionEvent.ACTION_UP:  
  38.         lastInterceptX = 0;  
  39.         lastInterceptY = 0;  
  40.     }  
  41.   
  42.     return deliver;  
  43. }  
 

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

 

[html]
  1. android:configChanges="orientation|keyboardHidden|navigation"  
 

 

在ActivityGroup中实现onConfigurationChanged()

[java]
  1. @Override  
  2. public void onConfigurationChanged(Configuration newConfig) {  
  3.     super.onConfigurationChanged(newConfig);  
  4.   
  5.     if (horizontalMenuView != null) {  
  6.         horizontalMenuView.changeOrientation();  
  7.     }  
  8. }  
 

在我们自定义View HorizontalMenuView中加入这个方法:

 

[java]
  1. /** 
  2.  * 改变方向 
  3.  */  
  4. public void changeOrientation() {  
  5.     int temp = screenWidth;  
  6.     screenWidth = screenHeight;  
  7.     screenHeight = temp;  
  8.       
  9.     childWidths[0] = screenWidth / 3 + 50;  
  10.     childWidths[1] = screenWidth;  
  11.       
  12.     snapToScreen(currentPage);  
  13. }  
 

OK, 大功告成!!

 

 

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

 

源代码下载地址:

 

转载于:https://www.cnblogs.com/MMLoveMeMM/articles/4102748.html

转载地址:https://blog.csdn.net/FLY_THINK2012/article/details/101840941 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Linux下的USB总线驱动(03)——USB鼠标驱动 usbmouse.c
下一篇:Linux下的USB总线驱动(01)——USB理论

发表评论

最新留言

能坚持,总会有不一样的收获!
[***.219.124.196]2024年03月13日 08时50分00秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章

android 图片点击变色,Android开发实现ListView点击item改变颜色功能示例 2019-04-21
android增删改查布局,Android之父_增删改查 2019-04-21
vowifi android开关,如何配置VoLTE, ViLTE and VoWifi(IMS config for VoLTE, ViLTE and VoWifi) 2019-04-21
电脑端的mafsvr服务关掉_网吧才是电脑优化的精髓!学会3招你也不用羡慕网吧的流畅了... 2019-04-21
html获取文件路径_HTML 文件路径 2019-04-21
mysql滴的一声就关了_关于mysql数据库在输入密码后,滴的一声直接退出界面的解决办法(详细办法)... 2019-04-21
mysql in 有序_mysql中的in排序 mysql按in中顺序来排序 2019-04-21
mysql 行转列 显示_mysql 行转列 (结果集以坐标显示) 2019-04-21
由于连接方在一段时间后没有正确答复或连接的主机_新风换气机使用效果不佳,为何?掌握正确使用方法就好了... 2019-04-21
mysql 查询姓王_MySQL查询语句练习题,测试足够用了 2019-04-21
mysql多实例脚本_mysql多实例脚本 2019-04-21
python如何生成excel文件夹_用python脚本通过excel生成文件夹树结构 2019-04-21
python获取post请求中的所有参数_Django从POST reques获取请求参数 2019-04-21
mysql加密复制_MySQL主从复制使用SSL加密 2019-04-21
python启动远端 exe_python打包exe开机自动启动的实例(windows) 2019-04-21
java当前路径_java获取当前路径的几种方法 2019-04-21
java web传递参数_Javaweb的八种传值方式 2019-04-21
java gui支持的包有哪两个_Java GUI 2019-04-21
java list详解_java集合List解析 2019-04-21
java坐标代码_java实现计算地理坐标之间的距离 2019-04-21