Android自定义控件案例汇总2(自定义开关、下拉刷新、侧滑菜单)

案例四 自定义开关:

功能介绍:本案例实现的功能是创建一个自定义的开关,可以自行决定开关的背景。当滑动开关时,开关的滑块可跟随手指移动。当手指松开后,滑块根据开关的状态,滑到最右边或者滑到最左边,同时保存开关的状态,将开关的状态回调给调用者。当然,上述功能系统给定的switch控件也可以实现。

实现步骤:

1. 写一个类继承view,重写两个参数的构造方法。在构造方法中指定工作空间,通过attrs.getAttributeResourceValue方法将java代码中的属性值和xml中的属性值联系起来。这样可以在xml文件中指定相关的属性值。重写onmeasure和ondraw方法,绘制图片。这里测量图片大小直接用setMeasuredDimension方法,获取图片本身的大小。
        2. 设置接口回调。对于图片来说,我们希望能够在调用者获取开关的状态,因此需要设置一个接口回调,用于监控开关的状态,当开关的状态发生变化时间调用。接口回调的优势在于调用者并不知道何时调用,所以在另一个文件中设置一个接口,在该文件触发事件。由于重写了接口的方法,因此,执行重写后的方法。这样就可以实现数据的回调。自定义控件中接口回调的应用较为广泛,几乎所有的控件都需要设置监听,且写法较为固定。
        3. 重写ontouchevent()方法。分析得知,开关由两部分组成,一部分是底座儿,一部分是划片而。当手指滑动时,划片儿应该跟随手指移动。当划片的左边小于底座左边坐标时,让划片左边的坐标和底座对齐,当划片的右边大于底座右边坐标时,让划片右边的坐标和底座对齐,这样保证划片不越界。当手指松开后,判断划片的中线坐标是在底座儿中线坐标的左边还是右边,以此来决定划片最终是停在左边还是右边。同时改变开关的状态,将开关的状态回调给控件的调用中,读取开关的状态。

代码实现。 主程序(调用者)中的代码:

package com.example.aswitch;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

  private MyToggleButton toggleButton;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    toggleButton = (MyToggleButton) findViewById(R.id.toggle_button);
    toggleButton.setOnStateChangedListener(new MyToggleButton.OnStateChangedListener() {
      @Override
      public void onStateChanged(boolean state) {
        Toast.makeText(MainActivity.this, state ? "开" : "关", Toast.LENGTH_SHORT).show();
      }
    });
  }
}

自定义开关的具体实现;

package com.example.aswitch;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

/**
 * Created by huang on 2016/12/1.
 */
public class MyToggleButton extends View {
  private Bitmap background;
  private Bitmap slideIcon;
  private boolean state;
  private OnStateChangedListener mOnStateChangedListener;
  private int backgroundWidth;
  private int backgroundHeight;
  private int slideIconWidth;
  private int slideIconHeight;
  private int slideIconLeft;
  private int maxSlideIconLeft;

  public MyToggleButton(Context context, AttributeSet attrs) {
    super(context, attrs);
    String namespace = "http://schemas.android.com/apk/res-auto";
    int slideBackgroundResId = attrs.getAttributeResourceValue(namespace, "slideBackground", -1);
    int slideIconResId = attrs.getAttributeResourceValue(namespace, "slideIcon", -1);
    if (slideBackgroundResId != -1 && slideIconResId != -1) {
      setSwitchImage(slideBackgroundResId, slideIconResId);
    }

    boolean state = attrs.getAttributeBooleanValue(namespace, "state", false);
    setState(state);
  }

  /**
   * 设置开关的图片
   * @param slideBackgroundResId 开关的背景图片资源id
   * @param slideIconResId 开关上面的滑块icon
   */
  public void setSwitchImage(int slideBackgroundResId, int slideIconResId) {
    background = BitmapFactory.decodeResource(getResources(), slideBackgroundResId);
    slideIcon = BitmapFactory.decodeResource(getResources(), slideIconResId);

    backgroundWidth = background.getWidth();
    backgroundHeight = background.getHeight();
    slideIconWidth = slideIcon.getWidth();
    slideIconHeight = slideIcon.getHeight();

    maxSlideIconLeft = backgroundWidth - slideIconWidth;
  }

  /** 设置开关按钮的状态 */
  public void setState(boolean state) {
    checkState(state);
    if (state) {
      slideIconLeft = maxSlideIconLeft;
    } else {
      slideIconLeft = 0;
    }
  }

  public void setOnStateChangedListener(OnStateChangedListener mOnStateChangedListener) {
    this.mOnStateChangedListener = mOnStateChangedListener;
  }

  /** 开关按钮状态改变的监听器 */
  public interface OnStateChangedListener {
    void onStateChanged(boolean state);
  }

  /**
   * 对View进行测量的方法
   */
  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(backgroundWidth, backgroundHeight);
  }

  /** 把View画出来的方法
   * @param canvas 画布
   * */
  @Override
  protected void onDraw(Canvas canvas) {
    int left = 0;
    int top = 0;
    canvas.drawBitmap(background, left, top, null);

    canvas.drawBitmap(slideIcon, slideIconLeft, 0, null);
  }

  @Override
  public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
      case MotionEvent.ACTION_DOWN:
      case MotionEvent.ACTION_MOVE:
        slideIconLeft = (int) (event.getX() - slideIconWidth / 2);

        if (slideIconLeft < 0) {
          slideIconLeft = 0;
        } else if (slideIconLeft > maxSlideIconLeft) {
          slideIconLeft = maxSlideIconLeft;
        }
        break;
      case MotionEvent.ACTION_UP:
        if (event.getX() < backgroundWidth / 2) {
          slideIconLeft = 0;
          checkState(false);
        } else {
          slideIconLeft = maxSlideIconLeft;
          checkState(true);
        }
        break;
    }
    invalidate();
    return true;
  }

  private void checkState(boolean state) {
    if (this.state != state) {
      this.state = state;

      if (mOnStateChangedListener != null) {
        mOnStateChangedListener.onStateChanged(state);
      }
    }
  }
}

布局文件的编写:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:huang="http://schemas.android.com/apk/res-auto"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <com.example.aswitch.MyToggleButton
    android:id="@+id/toggle_button"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    huang:slideBackground="@mipmap/slide_background2"
    huang:slideIcon="@mipmap/slide_icon2"
    huang:state="false" />

</RelativeLayout>

为了使布局文件中的属性有作用,还要单独在values文件夹中写一个attrs.xml文件,声明相关的属性。

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <declare-styleable name="MyToggleButton">
    <attr name="slideBackground" format="reference" />
    <attr name="slideIcon" format="reference" />
    <attr name="state" format="boolean" />
  </declare-styleable>
</resources>

案例五 下拉刷新的Listview:

功能介绍: 系统本身的Listview默认情况下是没有下拉刷新和上拉加载更多的功能。但是Listview有addHeadview和addFootView方法,因此可以为listview加头布局或者脚布局。对于头布局来说,有三个状态,下拉刷新、松开刷新、正在刷新,同时伴有相应的动画予以提示。刷新成功后,为listview添加一套信息。同样的脚布局加载成功后,在listview的最后一条数据后面,在加载一条信息。

实现步骤:

1. 写布局文件。这里除了在主界面中需要一个Listview外,还需要一个头部的布局文件和一个尾部的布局文件。初始状态时,头布局和脚布局均是隐藏的。当滑动事件出发的时候,调整头布局和脚布局的位置。这里。头布局和脚布局的根表签最好是使用Linearlayout进行包裹,否则在滑动显示是容易出现问题。
         2. 写一个类继承listview。初始化界面布局,通过View.measure(0, 0)方法主动触发测量,mesure内部会调用onMeasure,从而获取到头布局的高度。如步骤一所说,开始状态时,头布局是隐藏的,所以为头布局设置panding值,来使得头布局隐藏,只需要把setpadding中的第二个参数设置为负的头文件的高。同样的,显示头布局只需要把setpadding中的第二个参数设置为0。对于根布局的显示和隐藏同样采用上述的方法。当然也可以改变setpadding的第四个参数来控制显示和隐藏。
         3. 重写OntouchEvent()方法。手指按下时,记录下初始位置。手指滑动时在记录一个y坐标。通过两次坐标的差值,判断手指滑动的方向。在可见的第一个条目的position为0的时候,如果手指向下滑动,有两种状态,一个是下拉刷新,指的是头布局从不可见到刚好全部可见,一个是松开刷新,头布局全部可见后继续向下拖动,就会进入松开刷新状态。另外还有一种状态就是正在刷新,这种状态下,头布局刚好停留在顶部,维持一段时间。实际开发中,正在刷新是向服务器请求网络。这里添加的是假数据。不同的状态之间的转换还伴随有动画效果,因此,这里还写有补间动画来实现向上和向下的效果。
         4. 写一个滑动监听事件,在监听事件中进行脚布局逻辑的编写。当满足以下三种情况的时候则显示脚布局,一个ListView处于空闲状态,另一个是界面上可见的最后一条item是ListView中最后的一条item,还有一个是如果当前没有去做正在加载更多的事情。显示脚布局的时候,不仅要设置padding值,同时选中listview最后一个条目,这样就可以在界面上完整的显示。
         5. 最后要注意的是,无论是同布局,还是脚布局,都要对状态进行修改。所以,可以增加一个回调监听。表明数据刷新或者加载完成,可以隐藏头布局和脚布局了,同时,头布局的状态恢复为下拉刷新,脚布局不是正在加载。同时,停掉相关的动画,修改显示的状态。

主程序(调用者)的编写。一般刷新是请求数据库数据,这里直接给出模拟数据演示效果。

package com.example.pulltofreshlistview;

import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.widget.ArrayAdapter;

import com.example.pulltofreshlistview.view.PullToRefreshListView;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {
  private PullToRefreshListView listView;
  private ArrayList<String> datas;
  private ArrayAdapter<String> adapter;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    listView = (PullToRefreshListView) findViewById(R.id.list_view);

    datas = new ArrayList<String>();
    for (int i = 0; i < 20; i++) {
      datas.add("我又捡到钱了,好开心啊^_^ \t" + i);
    }

    adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, datas);
    listView.setAdapter(adapter);

    listView.setOnRefreshingListener(new PullToRefreshListView.OnRefreshingListener() {
      @Override
      public void onRefreshing() {
        reloadData();
      }

      @Override
      public void onLoadMore() {
        loadMore();
      }
    });
  }

  /**
   * 重新联网获取数据
   */
  protected void reloadData() {
    new Handler().postDelayed(new Runnable() {

      @Override
      public void run() {
        datas.add(0, "我是刷新出来的数据");
        adapter.notifyDataSetChanged();
        listView.onRefreshComplete();
      }
    }, 3000);
  }

  /**
   * 联网加载更多数据
   */
  protected void loadMore() {
    new Handler().postDelayed(new Runnable() {

      @Override
      public void run() {
        datas.add("我是加载更多出来的数据");
        adapter.notifyDataSetChanged();
        listView.onLoadmoreComplete();
      }
    }, 3000);
  }
}

下拉刷新listview的逻辑部分

package com.example.pulltofreshlistview.view;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.RotateAnimation;
import android.widget.AbsListView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.example.pulltofreshlistview.R;

/**
 * Created by huang on 2016/12/1.
 */

public class PullToRefreshListView extends ListView {

  private View headerView;
  private float downY;
  private int headerViewHeight;
  private static final int STATE_PULL_TO_REFRESH = 0;
  private static final int STATE_RELEASE_REFRESH = 1;
  private static final int STATE_REFRESHING = 2;
  private int currentState = STATE_PULL_TO_REFRESH;  // 默认是下拉刷新状态
  private ImageView iv_arrow;
  private ProgressBar progress_bar;
  private TextView tv_state;
  private RotateAnimation upAnim;
  private RotateAnimation downAnim;
  private OnRefreshingListener mOnRefreshingListener;
  private View footerView;
  private int footerViewHeight;
  private boolean loadingMore;

  public PullToRefreshListView(Context context, AttributeSet attrs) {
    super(context, attrs);
    initHeaderView();
    initFooterView();
  }

  private void initHeaderView() {
    headerView = View.inflate(getContext(), R.layout.header_view, null);
    iv_arrow = (ImageView) headerView.findViewById(R.id.iv_arrow);
    progress_bar = (ProgressBar) headerView.findViewById(R.id.progress_bar);
    showRefreshingProgressBar(false);
    tv_state = (TextView) headerView.findViewById(R.id.tv_state);
    headerView.measure(0, 0);
    headerViewHeight = headerView.getMeasuredHeight();
    hideHeaderView();
    super.addHeaderView(headerView);
    upAnim = createRotateAnim(0f, -180f);
    downAnim = createRotateAnim(-180f, -360f);
  }

  private void initFooterView() {
    footerView = View.inflate(getContext(), R.layout.footer_view, null);
    footerView.measure(0, 0);
    footerViewHeight = footerView.getMeasuredHeight();
    hideFooterView();
    super.addFooterView(footerView);

    super.setOnScrollListener(new OnScrollListener() {

      @Override
      public void onScrollStateChanged(AbsListView view, int scrollState) {
        if (scrollState == OnScrollListener.SCROLL_STATE_IDLE
            && getLastVisiblePosition() == getCount() - 1
            && loadingMore == false
            ) {
          loadingMore = true;
          showFooterView();
          setSelection(getCount() - 1);

          if (mOnRefreshingListener != null) {
            mOnRefreshingListener.onLoadMore();
          }
        }
      }

      @Override
      public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {

      }
    });
  }

  private void hideFooterView() {
    int paddingTop = -footerViewHeight;
    setFooterViewPaddingTop(paddingTop);
  }

  private void showFooterView() {
    int paddingTop = 0;
    setFooterViewPaddingTop(paddingTop);
  }

  private void setFooterViewPaddingTop(int paddingTop) {
    footerView.setPadding(0, paddingTop, 0, 0);
  }

  /**
   * 设置显示进度的圈圈
   *
   * @param showProgressBar 如果是true,则显示ProgressBar,否则的话显示箭头
   */
  private void showRefreshingProgressBar(boolean showProgressBar) {
    progress_bar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE);
    iv_arrow.setVisibility(!showProgressBar ? View.VISIBLE : View.GONE);

    if (showProgressBar) {
      iv_arrow.clearAnimation();  // 有动画的View要清除动画才能真正的隐藏
    }
  }

  /**
   * 创建旋转动画
   *
   * @param fromDegrees 从哪个角度开始转
   * @param toDegrees  转到哪个角度
   * @return
   */
  private RotateAnimation createRotateAnim(float fromDegrees, float toDegrees) {
    int pivotXType = RotateAnimation.RELATIVE_TO_SELF;
    int pivotYType = RotateAnimation.RELATIVE_TO_SELF;
    float pivotXValue = 0.5f;
    float pivotYValue = 0.5f;
    RotateAnimation ra = new RotateAnimation(fromDegrees, toDegrees, pivotXType, pivotXValue, pivotYType, pivotYValue);
    ra.setDuration(300);
    ra.setFillAfter(true);
    return ra;
  }

  /**
   * 隐藏HeaderView
   */
  private void hideHeaderView() {
    int paddingTop = -headerViewHeight;
    setHeaderViewPaddingTop(paddingTop);
  }

  /**
   * 显示HeaderView
   */
  private void showHeaderView() {
    int paddingTop = 0;
    setHeaderViewPaddingTop(paddingTop);
  }

  /**
   * 设置HeaderView的paddingTop
   *
   * @param paddingTop
   */
  private void setHeaderViewPaddingTop(int paddingTop) {
    headerView.setPadding(0, paddingTop, 0, 0);
  }

  @Override
  public boolean onTouchEvent(MotionEvent ev) {
    switch (ev.getAction()) {
      case MotionEvent.ACTION_DOWN:
        downY = ev.getY();
        break;
      case MotionEvent.ACTION_MOVE:
        if (currentState == STATE_REFRESHING) {
          return super.onTouchEvent(ev);
        }

        int fingerMoveDistanceY = (int) (ev.getY() - downY);    // 手指移动的距离
        if (fingerMoveDistanceY > 0 && getFirstVisiblePosition() == 0) {
          int paddingTop = -headerViewHeight + fingerMoveDistanceY;
          setHeaderViewPaddingTop(paddingTop);

          if (paddingTop < 0 && currentState != STATE_PULL_TO_REFRESH) {
            currentState = STATE_PULL_TO_REFRESH;
            tv_state.setText("下拉刷新");
            iv_arrow.startAnimation(downAnim);
            showRefreshingProgressBar(false);
          } else if (paddingTop >= 0 && currentState != STATE_RELEASE_REFRESH) {
            currentState = STATE_RELEASE_REFRESH;
            tv_state.setText("松开刷新");
            iv_arrow.startAnimation(upAnim);
            showRefreshingProgressBar(false);

          }
          return true;
        }
        break;
      case MotionEvent.ACTION_UP:
        if (currentState == STATE_RELEASE_REFRESH) {
          currentState = STATE_REFRESHING;
          tv_state.setText("正在刷新");
          showRefreshingProgressBar(true);
          showHeaderView();

          if (mOnRefreshingListener != null) {
            mOnRefreshingListener.onRefreshing();
          }
        } else if (currentState == STATE_PULL_TO_REFRESH) {
          hideHeaderView();
        }
        break;
    }
    return super.onTouchEvent(ev);
  }

  public void setOnRefreshingListener(OnRefreshingListener mOnRefreshingListener) {
    this.mOnRefreshingListener = mOnRefreshingListener;
  }

  /**
   * ListView刷新的监听器
   */
  public interface OnRefreshingListener {
    void onRefreshing();

    void onLoadMore();
  }

  /**
   * 联网刷新数据的操作已经完成了
   */
  public void onRefreshComplete() {
    hideHeaderView();
    currentState = STATE_PULL_TO_REFRESH;
    showRefreshingProgressBar(false);
  }

  /**
   * 加载更多新数据的操作已经完成了
   */
  public void onLoadmoreComplete() {
    hideFooterView();
    loadingMore = false;
  }
}

布局文件包含三个部分,listview主体部分:

<?xml version="1.0" encoding="utf-8"?>
<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=".MainActivity">

  <com.example.pulltofreshlistview.view.PullToRefreshListView
    android:id="@+id/list_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

</RelativeLayout>

头布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:orientation="horizontal"
  android:gravity="center_vertical" >

  <RelativeLayout
    android:layout_width="50dp"
    android:layout_height="50dp">

    <ImageView
      android:id="@+id/iv_arrow"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:src="@mipmap/arrow"
      android:layout_centerInParent="true"/>

    <ProgressBar
      android:id="@+id/progress_bar"
      style="@android:style/Widget.ProgressBar"
      android:indeterminateDrawable="@drawable/progress_medium_red"
      android:layout_width="28dp"
      android:layout_height="28dp"
      android:layout_centerInParent="true"
      android:visibility="gone"/>

  </RelativeLayout>

  <LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:orientation="vertical">

    <TextView
      android:id="@+id/tv_state"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:textColor="#FF0000"
      android:text="下拉刷新"/>

    <TextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:textColor="#666666"
      android:textSize="12sp"
      android:text="最后刷新时间:2015-07-25 19:59:39"
      android:layout_marginTop="4dp"/>

  </LinearLayout>

</LinearLayout>

脚布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:gravity="center"
  android:orientation="horizontal">

  <ProgressBar
    style="@android:style/Widget.ProgressBar"
    android:layout_width="28dp"
    android:layout_height="28dp"
    android:layout_marginBottom="8dp"
    android:layout_marginTop="8dp"
    android:indeterminateDrawable="@drawable/progress_medium_red" />

  <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginLeft="6dp"
    android:text="加载更多..."
    android:textColor="#FF0000" />

</LinearLayout>

案例六 侧滑菜单:

功能分析: 之前的案例大部分都只有一个控件,因此是通过继承view实现。侧滑菜单显然需要两个控件才能实现,一个用于加载主界面,一个用于加载侧滑界面。所以这里需要继承viewgroup。通过在主界面滑动来控制界面的显示和隐藏。

实现步骤:

1. 布局文件的书写。布局文件包含两个部分,主界面和侧滑界面。主界面中有一个标题栏,标题栏有图片按钮,文字组成。图片按牛同样可以控制侧边栏的显示和隐藏。侧边栏在ScrollView中添加一个Linearlayout,在linearlayout中纵向排列textview显示文本。当然,侧滑菜单同样可以用listview显示,这里为简单起见就不采用listview显示。此外,TextView要想响应点击事件,需要设置clickable为true。
          2. 创建一个类继承于ViewGroup。重写onmeasure()方法,测量测量控件大小,包括两个子控件。这里,侧滑菜单的宽在布局文件中写好了,主界面的宽适配手机界面。所以分别调用menu.measure(menuWidth, heightMeasureSpec)和main.measure(widthMeasureSpec, heightMeasureSpec)方法即可获取。排版容器中的子View。由于该自定义的控件包含不止一个子view,所以重写onlayout()方法是必不可少的。对子View进行排版,子View的0,0坐标是SlidingMenu的左上角。对侧滑菜单进行排版,菜单的left坐标在负的菜单宽的位置,菜单的top坐标在0的位置,菜单的right坐标在0的位置,菜单的bottom坐标在容器的最底边。对主界面进行排版,主界面的left坐标在0的位置,主界面的top坐标在0的位置,主界面的right坐标在容器的最右边,主界面的bottom坐标在容器的最底边。
          3. 重写onInterceptTouchEvent。本案例中只关心水平划动,所以如果水平移动距离比垂直移动距离大,则认为是水平移动,把事件拦截,不让ScrollView使用,此事件交由控件本身处理。这里有必要介绍一下事件分发。视图集合对于事件的分发,自上而下处理。ViewGroup拥有下面3个Touch相关方法,dispatchTouchEvent(MotionEvent ev)用于Touch事件的颁发,onInterceptTouchEvent(MotionEvent ev) 用于拦截Touch事件,onTouchEvent(MotionEvent event) 用于处理Touch事件。这里重写了onInterceptTouchEvent(),相应的还要重写ontouchevent()方法。
          4. 在ontouchevent()方法中实现滑动跟随的逻辑。滑动事件中,只关心横向滑动。按下时,记录下当前的x坐标。滑动时,再次记录当前x坐标,两者相减得到移动的距离。通过scrollTo()方法滑到相应的位置。系统给定的scrollTo()方法默认向右为负值,所以可重写scrollTo()方法保证移动为正值。代码优化。其实上述过程可以实现侧滑菜单的功能。当滑动过程略显生硬。这时就可以用到Scroller,这个类专门用于模拟滚动的数值。同时要配合computeScroll()方法使用。
          5. 还有一个菜单的开关按钮,要么开,要么关。开的时候完全显示侧滑菜单。关的时候,隐藏侧滑菜单。这点逻辑和ontouchevent()中手指抬起的逻辑一样。

主程序(调用者)的编写:

package com.example.slidingmenu;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity {

  private SlidingMenu sliding_menu;
  private LinearLayout ll_menu;
  private TextView tv_news;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    setContentView(R.layout.activity_main);
    sliding_menu = (SlidingMenu) findViewById(R.id.sliding_menu);
    ll_menu = (LinearLayout) findViewById(R.id.ll_menu);
    tv_news = (TextView) findViewById(R.id.tv_news);
    setCurrentSelectedMenuItem(tv_news);
  }

  /** 设置当前选择的菜单Item */
  private void setCurrentSelectedMenuItem(View menuItem) {
    for (int i = 0; i < ll_menu.getChildCount(); i++) {
      View child = ll_menu.getChildAt(i);
      child.setSelected(child == menuItem);
    }
  }

  /** 菜单列表中的某个菜单项被单击了 */
  public void onMenuItemClick(View v) {
    TextView textView = (TextView) v;
    Toast.makeText(this, textView.getText(), Toast.LENGTH_SHORT).show();
    setCurrentSelectedMenuItem(v);
  }

  /** 主界面上的菜单按钮被单击了 */
  public void onMenuToggleClick(View v) {
    sliding_menu.toggle();
  }

}

侧滑菜单的主体逻辑。

package com.example.slidingmenu;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Scroller;

/**
 * Created by huang on 2016/12/1.
 */
public class SlidingMenu extends ViewGroup {

  private View menu;
  private View main;
  private int menuWidth;
  private int downX;
  private int currentX;
  /** 这个类专门用于模拟滚动的数值 */
  private Scroller scroller;

  public SlidingMenu(Context context, AttributeSet attrs) {
    super(context, attrs);
    scroller = new Scroller(context);
  }

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);  // 测量容器自己的宽高

    menu = getChildAt(0); // 获取菜单容器
    main = getChildAt(1); // 获取主界面容器

    menuWidth = menu.getLayoutParams().width;   // 获取菜单的宽

    // 测量菜单
    menu.measure(menuWidth, heightMeasureSpec);

    // 测量主界面
    main.measure(widthMeasureSpec, heightMeasureSpec);
  }

  @Override
  protected void onLayout(boolean changed, int l, int t, int r, int b) {
    int menuLeft = -menuWidth;
    int menuTop = 0;
    int menuRight = 0;
    int menuBottom = b - t;
    menu.layout(menuLeft, menuTop, menuRight, menuBottom);

    int mainLeft = 0;
    int mainTop = 0;
    int mainRight = r - l;
    int mainBottom = b - t;
    main.layout(mainLeft, mainTop, mainRight, mainBottom);
  }

  /**
   * 让界面滚动到x的位置,传正数往右移,传负往左移
   * @param x
   */
  public void scrollTo(int x) {
    super.scrollTo(-x, 0);
  }

  /** 获取当前滑动到的位置 */
  public int getMyScrollX() {
    return -super.getScrollX();
  }

  @Override
  public boolean onInterceptTouchEvent(MotionEvent ev) {
    switch (ev.getAction()) {
      case MotionEvent.ACTION_DOWN:
        downY = (int) ev.getY();
        downX = (int) ev.getX();
        break;
      case MotionEvent.ACTION_MOVE:
        int distanceX = Math.abs((int) (ev.getX() - downX));
        int distanceY = Math.abs((int) (ev.getY() - downY));
        if (distanceX > distanceY) {
          return true;
        }
        break;
    }
    return super.onInterceptTouchEvent(ev);
  }

  @Override
  public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
      case MotionEvent.ACTION_DOWN:
        downX = (int) event.getX();
        break;
      case MotionEvent.ACTION_MOVE:
        int fingerMoveDistanceX = (int) event.getX() - downX;
        int destX = currentX + fingerMoveDistanceX;

        if (destX < 0) {
          destX = 0;
        } else if (destX > menuWidth){
          destX = menuWidth;
        }

        scrollTo(destX);
        break;
      case MotionEvent.ACTION_UP:
        if (getMyScrollX() < menuWidth / 2) {
          startScroll(0);
        } else {
          startScroll(menuWidth);
        }

        break;
    }
    return true;
  }

  int count;
  private int downY;

  /**
   * 以动画的方式滚动到指定的位置
   *
   * @param destX 要滑动到哪里(目标位置)
   */
  private void startScroll(int destX) {
    currentX = destX;
    int startX = getMyScrollX();
    int distatnceX = destX - startX;
    int duration = 800;
    scroller.startScroll(startX, 0, distatnceX, 0, duration);
    invalidate();
  }

  @Override
  public void computeScroll() {
    if (scroller.computeScrollOffset()) {
      int currX = scroller.getCurrX();
      scrollTo(currX);
      invalidate();
      count++;
    }
    System.out.println("count = " + count);
  }

  /** 菜单的开关按钮,要么开,要么关 */
  public void toggle() {
    if (getMyScrollX() > 0) {
      startScroll(0);
    } else {
      startScroll(menuWidth);
    }
  }
}

整体布局

<?xml version="1.0" encoding="utf-8"?>
<com.example.slidingmenu.SlidingMenu xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/sliding_menu"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <!-- 在SlidingMenu中的索引0 -->
  <include layout="@layout/menu" />

  <!-- 在SlidingMenu中的索引1 -->
  <include layout="@layout/main" />

</com.example.slidingmenu.SlidingMenu>

主界面布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical">

  <LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@mipmap/top_bar_bg"
    android:gravity="center_vertical"
    android:orientation="horizontal">

    <ImageView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:onClick="onMenuToggleClick"
      android:src="@mipmap/main_back" />

    <View
      android:layout_width="1dp"
      android:layout_height="match_parent"
      android:layout_marginBottom="6dp"
      android:layout_marginTop="6dp"
      android:background="@mipmap/top_bar_divider" />

    <TextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_marginLeft="12dp"
      android:text="新闻"
      android:textColor="@android:color/white"
      android:textSize="34sp" />

  </LinearLayout>

  <TextView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:text="为了一个小馒头,友谊的小船说翻就翻"
    android:textSize="30sp" />
</LinearLayout>

侧滑界面布局

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="240dp"
  android:layout_height="match_parent"
  android:background="@mipmap/menu_bg">

  <LinearLayout
    android:id="@+id/ll_menu"
    android:layout_width="240dp"
    android:layout_height="wrap_content"
    android:orientation="vertical" >

    <TextView
      android:id="@+id/tv_news"
      android:text="新闻"
      style="@style/menu_item"
      android:drawableLeft="@mipmap/tab_news"/>

    <TextView
      android:text="订阅"
      style="@style/menu_item"
      android:drawableLeft="@mipmap/tab_read"/>

    <TextView
      android:text="本地"
      style="@style/menu_item"
      android:drawableLeft="@mipmap/tab_local"/>

    <TextView
      android:text="跟贴"
      style="@style/menu_item"
      android:drawableLeft="@mipmap/tab_ties"/>

    <TextView
      android:text="图片"
      style="@style/menu_item"
      android:drawableLeft="@mipmap/tab_pics"/>

    <TextView
      android:text="话题"
      style="@style/menu_item"
      android:drawableLeft="@mipmap/tab_ugc"/>

    <TextView
      android:text="投票"
      style="@style/menu_item"
      android:drawableLeft="@mipmap/tab_vote"/>

    <TextView
  android:text="聚合阅读"
  style="@style/menu_item"
  android:drawableLeft="@mipmap/tab_focus"/>

  </LinearLayout>
  </ScrollView>

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android 中 SwipeLayout一个展示条目底层菜单的侧滑控件源码解析

    由于项目上的需要侧滑条目展示收藏按钮,记得之前代码家有写过一个厉害的开源控件 AndroidSwipeLayout 本来准备直接拿来使用,但是看过 issue 发现现在有不少使用者反应有不少的 bug ,而且代码家现在貌似也不进行维护了.故自己实现了一个所要效果的一个控件.因为只是实现我需要的效果,所以大家也能看到,代码里有不少地方我是写死的.希望对大家有些帮助.而且暂时也不需要 AndroidSwipeLayout 大而全的功能,算是变相给自己做的项目精简代码了. 完整示例代码请看:GitHu

  • Android仿微信联系人列表字母侧滑控件

    仿微信联系人列表字母侧滑控件, 侧滑控件参考了以下博客: Android实现ListView的A-Z字母排序和过滤搜索功能 首先分析一下字母侧滑控件应该如何实现,根据侧滑控件的高度和字母的数量来平均计算每个字母应该占据的高度. 在View的onDraw()方法下绘制每一个字母 protected void onDraw(Canvas canvas) { super.onDraw(canvas); int height = getHeight();// 获取对应高度 int width = get

  • Android使用自定义控件HorizontalScrollView打造史上最简单的侧滑菜单

    侧滑菜单在很多应用中都会见到,最近QQ5.0侧滑还玩了点花样~~对于侧滑菜单,一般大家都会自定义ViewGroup,然后隐藏菜单栏,当手指滑动时,通过Scroller或者不断的改变leftMargin等实现:多少都有点复杂,完成以后还需要对滑动冲突等进行处理~~今天给大家带来一个简单的实现,史上最简单有点夸张,但是的确是我目前遇到过的最简单的一种实现~~~ 1.原理分析 既然是侧滑,无非就是在巴掌大的屏幕,塞入大概两巴掌大的布局,需要滑动可以出现另一个,既然这样,大家为啥不考虑使用Android

  • Android控件View打造完美的自定义侧滑菜单

    一.概述 在App中,经常会出现侧滑菜单,侧滑滑出View等效果,虽然说Android有很多第三方开源库,但是实际上咱们可以自己也写一个自定义的侧滑View控件,其实不难,主要涉及到以下几个要点: 1.对Android中Window类中的DecorView有所了解 2.对Scroller类实现平滑移动效果 3.自定义ViewGroup的实现 首先来看看效果图吧:     下面现在就来说说这里咱们实现侧滑View的基本思路吧,这里我采用的是自定义一个继承于RelativeLayout的控件叫做XC

  • Android仿微信联系人按字母排序

    App只要涉及到联系人的界面,几乎都是按照字母排序以及导航栏的方式.既然这个需求这么火,于是开始学习相关内容,此篇文章是我通过参考网上资料独立编写和总结的,希望多多少少对大家有所帮助,写的不好,还请各位朋友指教. 效果图如下: 实现这个效果,需要三个知识点 : 1:将字符串 进行拼音分类 2:ExpandableListView 二级扩展列表 3:右边字母分类View 我们先一个一个来了解解决方案,再上代码. 实现字母分类: 字母分类又分为三个小要点:一个是将中文转化为拼音,一个是实现按照字母的

  • Android自定义控件简单实现侧滑菜单效果

    侧滑菜单在很多应用中都会见到,最近QQ5.0侧滑还玩了点花样~~对于侧滑菜单,一般大家都会自定义ViewGroup,然后隐藏菜单栏,当手指滑动时,通过Scroller或者不断的改变leftMargin等实现:多少都有点复杂,完成以后还需要对滑动冲突等进行处理~~今天给大家带来一个简单的实现,史上最简单有点夸张,但是的确是我目前遇到过的最简单的一种实现~~~ 1.原理分析 既然是侧滑,无非就是在巴掌大的屏幕,塞入大概两巴掌大的布局,需要滑动可以出现另一个,既然这样,大家为啥不考虑使用Android

  • Android实现屏蔽微信拉黑和删除联系人功能示例

    Android实现屏蔽微信拉黑和删除联系人功能,废话不多说,具体如下: 实现效果: 让微信永远弹不出那个删除的对话框不就相当于屏蔽掉该功能了吗?哈哈效果如图: 实现原理: 1.我们知道,其实微信每次删除联系人都会弹出此页面 2.如果你对AccessibilityService有过了解或者有看过我之前的两篇博客,你会知道,其实每次弹出这个框,都会触发AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED这个事件,所以我们只要在每次触发该事件的时候进行判断当前页面是

  • Android仿微信联系人字母排序效果

    本文实例为大家分享了Android联系人字母排序的具体代码,供大家参考,具体内容如下 实现思路:首先说下布局,整个是一个相对布局,最下面是一个listview,listview上面是一个自定义的view(右边显示字母),最上面是一个textview(屏幕中间的方块). 首先说一下右边自定义view,字母是画到view上面的,首先计算一下view的高度,然后除以存放字母数组的长的,得到每个字符的高度:每个字母的宽度都是一样的,所以这里直接设置30sp: listview显示的是108个梁山好汉的名

  • Android自定义控件开发实战之实现ListView下拉刷新实例代码

    这篇博客为大家介绍一个android常见的功能--ListView下拉刷新: 首先下拉未松手时候手机显示这样的界面: 下面的代码是自定的扎样的控件: <span style="font-family: comic sans ms,sans-serif; font-size: 16px;">package com.dhsr.smartID.view; import android.content.Context; import android.util.AttributeSe

  • Android自定义控件案例汇总2(自定义开关、下拉刷新、侧滑菜单)

    案例四 自定义开关: 功能介绍:本案例实现的功能是创建一个自定义的开关,可以自行决定开关的背景.当滑动开关时,开关的滑块可跟随手指移动.当手指松开后,滑块根据开关的状态,滑到最右边或者滑到最左边,同时保存开关的状态,将开关的状态回调给调用者.当然,上述功能系统给定的switch控件也可以实现. 实现步骤: 1. 写一个类继承view,重写两个参数的构造方法.在构造方法中指定工作空间,通过attrs.getAttributeResourceValue方法将java代码中的属性值和xml中的属性值联

  • Android程序开发之使用PullToRefresh实现下拉刷新和上拉加载

    PullToRefresh是一套实现非常好的下拉刷新库,它支持: 1.ListView 2.ExpandableListView 3.GridView 4.WebView 等多种常用的需要刷新的View类型,而且使用起来也十分方便. (下载地址:https://github.com/chrisbanes/Android-PullToRefresh) 下载完成,将它导入到eclipse中,作为一个library导入到你的工程中就好了. 一.废话少说,下拉刷新go. 1.在你的布局文件中加上你想用的

  • Android自定义控件案例汇总1(菜单、popupwindow、viewpager)

    自定义控件是根据自己的需要自己来编写控件.安卓自带的控件有时候无法满足你的需求,这种时候,我们只能去自己去实现适合项目的控件.同时,安卓也允许你去继承已经存在的控件或者实现你自己的控件以便优化界面和创造更加丰富的用户体验.在平常的项目中,我们 人为的把自定义控件分为两种:一种是组合方式实现.一种是通过继承view或viewgroup及其子类实现.两者都可以实现我们想要的效果,因此,我们可以根据自己的需求,选择合适的方案.本文以案例的形式来显示几种较为常见的自定义控件. 案例一 优酷菜单: 功能介

  • Android仿新浪微博自定义ListView下拉刷新(4)

    自定义PullToRefreshListView继承ListView,在ListView头部添加一个下拉的头部布局.跟ListView用法完全一致. 该自定义Listview代码详解具体可参考: http://www.jb51.net/article/97845.htm 此处详细介绍Adapter的详细代码. 1.首先给Adapter绑定ListView布局. 2.其次创建一个层次对应组件的类,将对应的组件和对象进行关联,提高效率. 3.然后跟陆获得的图片路径异步下载图片,由于不知道该微博图片的

  • Android使用RecyclerView实现自定义列表、点击事件以及下拉刷新

    Android使用RecyclerView 1. 什么是RecyclerView RecyclerView 是 Android-support-v7-21 版本中新增的一个 Widgets,官方对于它的介绍则是:RecyclerView 是 ListView 的升级版本,更加先进和灵活. 简单来说就是:RecyclerView是一种新的视图组,目标是为任何基于适配器的视图提供相似的渲染方式.它被作为ListView和GridView控件的继承者,在最新的support-V7版本中提供支持. 2.

  • Android自定义控件ListView下拉刷新的代码

    ListView在实际实用中,一般都会有下新刷新和上拉加载的动态效果,今天要学的就是如何自定义带下拉刷新的ListView. 原理解析:一般将有下拉刷新的listview分成四种不同的状态来进行不同的显示效果. 1.完成状态done:listview正常显示状态 2.下拉状态pull:listview正在下拉时的状态 3.释放状态release:listview下拉后松开的状态 4.更新状态refreshing:listview下拉后加载数据时的状态 实现步骤: 自定义CustomListVie

  • Android RecyclerView实现下拉刷新和上拉加载

    RecyclerView已经出来很久了,许许多多的项目都开始从ListView转战RecyclerView,那么,上拉加载和下拉刷新是一件很有必要的事情. 在ListView上,我们可以通过自己添加addHeadView和addFootView去添加头布局和底部局实现自定义的上拉和下拉,或者使用一些第三方库来简单的集成,例如Android-pulltorefresh或者android-Ultra-Pull-to-Refresh,后者的自定义更强,但需要自己实现上拉加载. 而在下面我们将用两种方式

  • Android使用recyclerview打造真正的下拉刷新上拉加载效果

    前言 前段时间需要用到recyclerview,就想找个封装好的下拉刷新,上拉加载的库,结果愣是没找到,便自己写了一个. 注意:我说的是"上拉加载",不是滑到底部自动加载. 虽然现在自动加载是主流和趋势,但也不排除有时候就需要用到上拉加载啊,毕竟林子大了,什么样的产品经理都有对吧. 代码写好后,准备发布到bintray的时候,向同事征求这个项目的名字,同事说:"就叫DZTRecyclerview!" 不解,同事解释:"叼炸天Recyclerview!&qu

  • Android之RecyclerView轻松实现下拉刷新和加载更多示例

    今天研究了下RecyclerView的滑动事件,特别是下拉刷新和加载更多事件,在现在几乎所有的APP显示数据列表时都用到了.自定义RecyclerView下拉刷新和加载更多听上去很复杂,实际上并不难,只要是对滑动事件的监听和处理. 一.自定义RecyclerView实现下拉刷新和加载更多 1.如何判断RecyclerView是在上滑还是下滑 在RecyclerView的OnScrollListener滑动事件监听中有个好用的方法,就是onScrolled(RecyclerView recycle

随机推荐