欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android自定义ViewGroup(侧滑菜单)详解及简单实例

程序员文章站 2023-12-17 14:58:10
自定义侧滑菜单的简单实现 不少app中都有这种侧滑菜单,例如qq这类的,比较有名开源库如slidingmenu。 有兴趣的可以去研究研究这个开源库。 这里我们将...

自定义侧滑菜单的简单实现

不少app中都有这种侧滑菜单,例如qq这类的,比较有名开源库如slidingmenu。

有兴趣的可以去研究研究这个开源库。

这里我们将一种自己的实现方法,把学习的 东西做个记录,o(∩_∩)o!

首先看效果图:

Android自定义ViewGroup(侧滑菜单)详解及简单实例

这里我们实现的侧滑菜单,是将左侧隐藏的菜单和主面板看作一个整体来实现的,而左侧隐藏的菜单和主面板相当于是这个自定义view的子view。

首先来构造该自定义view的布局:

自定义的slidemenuview包含两个子view,一个是menuview,另一个是mainview(主面板)。

<relativelayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  tools:context="${relativepackage}.${activityclass}" >

  <com.wind.view.slidemenuview
    android:id="@+id/slidemenu"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <include layout="@layout/layout_menu"/>

    <include layout="@layout/layout_main"/>
  </com.wind.view.slidemenuview>

</relativelayout>

接着我们需要实现slidemenuview的 java代码。

自定义view,需要继承某个父类,通过重写父类的某些方法来增强父类的功能。

这里我们选择继承viewgroup,一般自定义view,需要重写onmeasure,onlayout和ondraw,正常情况下只要重写ondraw就可以了,特殊情况下,需要重写onmeasure 和onlayout。

package com.wind.view;

import android.content.context;
import android.util.attributeset;
import android.util.log;
import android.view.motionevent;
import android.view.view;
import android.view.viewgroup;
import android.widget.scroller;

public class slidemenuview extends viewgroup { // framelayout {
  private static final string tag = "slidemenuview";
  private view menuview, mainview;
  private int menuwidth = 0;

  private scroller scroller;

  public slidemenuview(context context) {
    super(context);
    init();
  }

  public slidemenuview(context context, attributeset attrs) {
    super(context, attrs);
    init();
  }

  private void init() {
    scroller = new scroller(getcontext());
  }

  /**
   * 当一级的子view全部加载玩后调用,可以用于初始化子view的引用 
   * 
   */
  @override
  protected void onfinishinflate() {
    super.onfinishinflate();
    menuview = getchildat(0);
    mainview = getchildat(1);
    menuwidth = menuview.getlayoutparams().width;

    log.d(tag, "onfinishinflate() menuwidth: " + menuwidth);
  }

    /**
     * widthmeasurespec和heightmeasurespec是系统测量slidemenu时传入的参数,
     * 这2个参数测量出的宽高能让slidemenu充满窗体,其实是正好等于屏幕宽高
     */
  @override
  protected void onmeasure(int widthmeasurespec, int heightmeasurespec) {
    super.onmeasure(widthmeasurespec, heightmeasurespec);

    log.d(tag, "onmeasure: widthmeasurespec: " + widthmeasurespec
        + "heightmeasurespec: " + heightmeasurespec);

    // int measurespec = measurespec.makemeasurespec(menuwidth,
    // measurespec.exactly);
    //
    // log.d(tag,"onmeasure: measurespec: " +measurespec);
    // 测量所有子view的宽高
    // 通过getlayoutparams方法可以获取到布局文件中指定宽高
    menuview.measure(widthmeasurespec, heightmeasurespec);
    // 直接使用slidemenu的测量参数,因为它的宽高都是充满父窗体
    mainview.measure(widthmeasurespec, heightmeasurespec);

  }

     /**
     * 重新摆放子view的位置
     * l: 当前子view的左边在父view的坐标系中的x坐标
     * t: 当前子view的顶边在父view的坐标系中的y坐标
     */
  @override
  protected void onlayout(boolean changed, int l, int t, int r, int b) {
    log.d(tag, "onlayout() changed: " + changed + "l: " + l + "t: " + t
        + "r: " + r + "b: " + b);
    log.d(tag,"menuview.getmeasuredheight(): " + menuview.getmeasuredheight());
    menuview.layout(-menuwidth, 0, 0, menuview.getmeasuredheight());
    mainview.layout(0, 0, r, b);
  }

 

 
  private int downx;

  @override
  public boolean ontouchevent(motionevent event) {
    switch (event.getaction()) {
    case motionevent.action_down:
      downx = (int) event.getx();
      break;

    case motionevent.action_move:
      int movex = (int) event.getx();
      int deltax = (movex - downx);
      log.e(tag, "scrollx: " + getscrollx());

      int newscrollx = getscrollx() - deltax;
      //使得slidemenuview不会滑动出界
      if(newscrollx > 0) newscrollx = 0;
      if(newscrollx < -menuwidth) newscrollx = -menuwidth;

      scrollto(newscrollx, 0);
      log.e(tag, "movex: " + movex);
      downx = movex;
      break;

    case motionevent.action_up:

      //①.使用自定义动画
//     scrollanimation scrollanimation;
//     if(getscrollx()>-menuwidth/2){
//       //关闭菜单
////        scrollto(0, 0);
//       scrollanimation = new scrollanimation(this, 0);
//     }else {
//       //打开菜单
////        scrollto(-menuwidth, 0);
//       scrollanimation = new scrollanimation(this, -menuwidth);
//     }
//     startanimation(scrollanimation);


      //②使用scroller
      if(getscrollx()>-menuwidth/2){
//       //关闭菜单
        closemenu();
      }else {
        //打开菜单
        openmenu();
      }
      break;
    }

    return true;
  }

  private void openmenu() {
    log.d(tag, "openmenu...");
    scroller.startscroll(getscrollx(), 0, -menuwidth-getscrollx(), 400);
    invalidate();
  }

  private void closemenu() {
    log.d(tag, "closemenu...");
    scroller.startscroll(getscrollx(), 0, 0-getscrollx(), 400);
    invalidate();
  }

    /**
     * scroller不主动去调用这个方法
     * 而invalidate()可以掉这个方法
     * invalidate->draw->computescroll
     */
  @override
  public void computescroll() {
    super.computescroll();
    if(scroller.computescrolloffset()){//返回true,表示动画没结束
      scrollto(scroller.getcurrx(), 0);
      invalidate();
    }
  }
  //用于在activity中来控制菜单的状态
  public void switchmenu() {
    if(getscrollx() == 0) {
      openmenu();
    } else {
      closemenu();
    }
  }
}

  1. 这里继承viewgroup,由于子view都是利用系统的控件填充,所以不需要重写ondraw方法。
  2. 由于是继承自viewgroup,需要实现onmeasure来测量两个子view的高度和宽度。我们还可以继承framelayout,则不需要实现onmeasure,因为framelayout已经帮我们实现onmeasure。
  3. 重写onlayout方法,重新定义自定义的位置摆放,左侧的侧滑菜单需要使其处于隐藏状态。
  4. 重写ontouch方法,通过监测touch的up和down来滑动view来实现侧滑的效果。

关于平滑滚动

我们可以采用scroller类中的startscroll来实现平滑滚动,同样我们可以使用自定义动画的方式来实现。

package com.wind.view;

import android.view.view;
import android.view.animation.animation;
import android.view.animation.transformation;

/**
 * 让指定view在一段时间内scrollto到指定位置
 * @author administrator
 *
 */
public class scrollanimation extends animation{

  private view view;
  private int targetscrollx;
  private int startscrollx;
  private int totalvalue;

  public scrollanimation(view view, int targetscrollx) {
    super();
    this.view = view;
    this.targetscrollx = targetscrollx;

    startscrollx = view.getscrollx();
    totalvalue = this.targetscrollx - startscrollx;

    int time = math.abs(totalvalue);
    setduration(time);
  }

 

  /**
   * 在指定的时间内一直执行该方法,直到动画结束
   * interpolatedtime:0-1 标识动画执行的进度或者百分比
   * time : 0  - 0.5 - 0.7 -  1
   * value: 10 - 60 - 80 - 110
   * 当前的值 = 起始值 + 总的差值*interpolatedtime
   */
  @override
  protected void applytransformation(float interpolatedtime,
      transformation t) {
    super.applytransformation(interpolatedtime, t);
    int currentscrollx = (int) (startscrollx + totalvalue*interpolatedtime);
    view.scrollto(currentscrollx, 0);
  }
}

如上面的代码:

通过自定义动画来让view在一段时间内重复执行这个动作。

关于getscrollx

将view向右移动的时候,通过view.getscrollx得到的值是负的。

其实可以这样理解:

*getscrollx()表示的是当前的屏幕x坐标的最小值-移动的距离(向右滑动时移动的距离为正值,

向左滑动时移动的距离为负值)。*

对scrollto和scrollby的理解

我们查看view的源码发现,scrollby其实调用的就是scrollto,scrollto就是把view移动到屏幕的x和y位置,也就是绝对位置。而scrollby其实就是调用的scrollto,但是参数是当前mscrollx和mscrolly加上x和y的位置,所以scrollby调用的是相对于mscrollx和mscrolly的位置。

我们在上面的代码中可以看到当我们手指不放移动屏幕时,就会调用scrollby来移动一段相对的距离。而当我们手指松开后,会调用mscroller.startscroll(munboundedscrollx, 0, delta, 0, duration);来产生一段动画来移动到相应的页面,在这个过程中系统回不断调用computescroll(),我们再使用scrollto来把view移动到当前scroller所在的绝对位置。

/**
   * set the scrolled position of your view. this will cause a call to
   * {@link #onscrollchanged(int, int, int, int)} and the view will be
   * invalidated.
   * @param x the x position to scroll to
   * @param y the y position to scroll to
   */
  public void scrollto(int x, int y) {
    if (mscrollx != x || mscrolly != y) {
      int oldx = mscrollx;
      int oldy = mscrolly;
      mscrollx = x;
      mscrolly = y;
      invalidateparentcaches();
      onscrollchanged(mscrollx, mscrolly, oldx, oldy);
      if (!awakenscrollbars()) {
        invalidate(true);
      }
    }
  }
  /**
   * move the scrolled position of your view. this will cause a call to
   * {@link #onscrollchanged(int, int, int, int)} and the view will be
   * invalidated.
   * @param x the amount of pixels to scroll by horizontally
   * @param y the amount of pixels to scroll by vertically
   */
  public void scrollby(int x, int y) {
    scrollto(mscrollx + x, mscrolly + y);
  }

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

上一篇:

下一篇: