Android自定义转盘菜单效果

最近由于公司项目需要,需要开发一款转盘菜单,费了好大功夫搞出来了,下面分享下

样图

具体功能如下:

import android.graphics.Color;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v7.app.AppCompatActivity;
import android.widget.Toast;

import com.hitomi.smlibrary.OnSpinMenuStateChangeListener;
import com.hitomi.smlibrary.TurnTableMenu;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

  private TurnTableMenu turnTableMenu;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    turnTableMenu = (TurnTableMenu) findViewById(R.id.spin_menu);

    // 设置页面标题
    List<String> hintStrList = new ArrayList<>();
    hintStrList.add("热门信息");
    hintStrList.add("实时新闻");
    hintStrList.add("我的论坛");
    hintStrList.add("我的信息");
    hintStrList.add("环游世界");
    hintStrList.add("阅读空间");
    hintStrList.add("欢乐空间");
    hintStrList.add("系统设置");

    turnTableMenu.setHintTextStrList(hintStrList);
    turnTableMenu.setHintTextColor(Color.parseColor("#FFFFFF"));
    turnTableMenu.setHintTextSize(14);

    // 设置页面适配器
    final List<Fragment> fragmentList = new ArrayList<>();
    fragmentList.add(Fragment1.newInstance());
    fragmentList.add(Fragment2.newInstance());
    fragmentList.add(Fragment3.newInstance());
    fragmentList.add(Fragment4.newInstance());
    fragmentList.add(Fragment5.newInstance());
    fragmentList.add(Fragment6.newInstance());
    fragmentList.add(Fragment7.newInstance());
    fragmentList.add(Fragment8.newInstance());
    FragmentPagerAdapter fragmentPagerAdapter = new FragmentPagerAdapter(getSupportFragmentManager()) {
      @Override
      public Fragment getItem(int position) {
        return fragmentList.get(position);
      }

      @Override
      public int getCount() {
        return fragmentList.size();
      }
    };
    turnTableMenu.setFragmentAdapter(fragmentPagerAdapter);

    // 设置菜单状态改变时的监听器
    turnTableMenu.setOnSpinMenuStateChangeListener(new OnSpinMenuStateChangeListener() {
      @Override
      public void onMenuOpened() {
        Toast.makeText(MainActivity.this, "SpinMenu opened", Toast.LENGTH_SHORT).show();
      }

      @Override
      public void onMenuClosed() {
        Toast.makeText(MainActivity.this, "SpinMenu closed", Toast.LENGTH_SHORT).show();
      }
    });
  }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<com.hitomi.smlibrary.TurnTableMenu xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  xmlns:tools="http://schemas.android.com/tools"
  android:id="@+id/spin_menu"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  app:hint_text_color="#FFFFFF"
  app:hint_text_size="14sp"
  app:scale_ratio="0.36"
  tools:context="com.hitomi.spinmenu.MainActivity">

  <FrameLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#333a4a"></FrameLayout>

</com.hitomi.smlibrary.TurnTableMenu>

3.自定义View TurnTableMenu

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.os.Build;
import android.support.annotation.IdRes;
import android.support.v4.view.GestureDetectorCompat;
import android.support.v4.view.PagerAdapter;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;

import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;

public class TurnTableMenu extends FrameLayout {

  static final String TAG = "SpinMenu";

  static final String TAG_ITEM_CONTAINER = "tag_item_container";

  static final String TAG_ITEM_PAGER = "tag_item_pager";

  static final String TAG_ITEM_HINT = "tag_item_hint";

  static final int MENU_STATE_CLOSE = -2;

  static final int MENU_STATE_CLOSED = -1;

  static final int MENU_STATE_OPEN = 1;

  static final int MENU_STATE_OPENED = 2;

  /**
   * 左右菜单 Item 移动动画的距离
   */
  static final float TRAN_SKNEW_VALUE = 160;

  /**
   * Hint 相对 页面的上外边距
   */
  static final int HINT_TOP_MARGIN = 15;

  /**
   * 可旋转、转动布局
   */
  private TurnTableMenuLayout turnTableMenuLayout;

  /**
   * 菜单打开关闭动画帮助类
   */
  private TurnTableMenuAnimator turnTableMenuAnimator;

  /**
   * 页面适配器
   */
  private PagerAdapter pagerAdapter;

  /**
   * 手势识别器
   */
  private GestureDetectorCompat menuDetector;

  /**
   * 菜单状态改变监听器
   */
  private OnSpinMenuStateChangeListener onSpinMenuStateChangeListener;

  /**
   * 缓存 Fragment 的集合,供 {@link #pagerAdapter} 回收使用
   */
  private List pagerObjects;

  /**
   * 菜单项集合
   */
  private List<SMItemLayout> smItemLayoutList;

  /**
   * 页面标题字符集合
   */
  private List<String> hintStrList;

  /**
   * 页面标题字符尺寸
   */
  private int hintTextSize = 14;

  /**
   * 页面标题字符颜色
   */
  private int hintTextColor = Color.parseColor("#666666");

  /**
   * 默认打开菜单时页面缩小的比率
   */
  private float scaleRatio = .36f;

  /**
   * 控件是否初始化的标记变量
   */
  private boolean init = true;

  /**
   * 是否启用手势识别
   */
  private boolean enableGesture;

  /**
   * 当前菜单状态,默认为打开
   */
  private int menuState = MENU_STATE_CLOSED;

  /**
   * 滑动与触摸之间的阀值
   */
  private int touchSlop = 8;

  private OnSpinSelectedListener onSpinSelectedListener = new OnSpinSelectedListener() {
    @Override
    public void onSpinSelected(int position) {
      log("SpinMenu position:" + position);
    }
  };

  private OnMenuSelectedListener onMenuSelectedListener = new OnMenuSelectedListener() {
    @Override
    public void onMenuSelected(SMItemLayout smItemLayout) {
      closeMenu(smItemLayout);
    }
  };

  private GestureDetector.SimpleOnGestureListener menuGestureListener = new GestureDetector.SimpleOnGestureListener() {

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
      if (Math.abs(distanceX) < touchSlop && distanceY < -touchSlop * 3) {
        openMenu();
      }
      return true;
    }
  };

  public TurnTableMenu(Context context) {
    this(context, null);
  }

  public TurnTableMenu(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
  }

  public TurnTableMenu(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);

    TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TurnTableMenu);
    scaleRatio = typedArray.getFloat(R.styleable.TurnTableMenu_scale_ratio, scaleRatio);
    hintTextSize = typedArray.getDimensionPixelSize(R.styleable.TurnTableMenu_hint_text_size, hintTextSize);
    hintTextSize = px2Sp(hintTextColor);
    hintTextColor = typedArray.getColor(R.styleable.TurnTableMenu_hint_text_color, hintTextColor);
    typedArray.recycle();

    pagerObjects = new ArrayList();
    smItemLayoutList = new ArrayList<>();
    menuDetector = new GestureDetectorCompat(context, menuGestureListener);

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.DONUT) {
      ViewConfiguration conf = ViewConfiguration.get(getContext());
      touchSlop = conf.getScaledTouchSlop();
    }
  }

  @Override
  protected void onFinishInflate() {
    super.onFinishInflate();

    @IdRes final int smLayoutId = 0x6F060505;
    ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT);
    turnTableMenuLayout = new TurnTableMenuLayout(getContext());
    turnTableMenuLayout.setId(smLayoutId);
    turnTableMenuLayout.setLayoutParams(layoutParams);
    turnTableMenuLayout.setOnSpinSelectedListener(onSpinSelectedListener);
    turnTableMenuLayout.setOnMenuSelectedListener(onMenuSelectedListener);
    addView(turnTableMenuLayout);
  }

  @Override
  protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);

    if (init && smItemLayoutList.size() > 0) {
      // 根据 scaleRatio 去调整菜单中 item 视图的整体大小
      int pagerWidth = (int) (getMeasuredWidth() * scaleRatio);
      int pagerHeight = (int) (getMeasuredHeight() * scaleRatio);
      SMItemLayout.LayoutParams containerLayoutParams = new SMItemLayout.LayoutParams(pagerWidth, pagerHeight);
      SMItemLayout smItemLayout;
      FrameLayout frameContainer;
      TextView tvHint;
      for (int i = 0; i < smItemLayoutList.size(); i++) {
        smItemLayout = smItemLayoutList.get(i);
        frameContainer = (FrameLayout) smItemLayout.findViewWithTag(TAG_ITEM_CONTAINER);
        frameContainer.setLayoutParams(containerLayoutParams);
        if (i == 0) { // 初始菜单的时候,默认显示第一个 Fragment
          FrameLayout pagerLayout = (FrameLayout) smItemLayout.findViewWithTag(TAG_ITEM_PAGER);
          // 先移除第一个包含 Fragment 的布局
          frameContainer.removeView(pagerLayout);

          // 创建一个用来占位的 FrameLayout
          FrameLayout holderLayout = new FrameLayout(getContext());
          LinearLayout.LayoutParams pagerLinLayParams = new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT);
          holderLayout.setLayoutParams(pagerLinLayParams);

          // 将占位的 FrameLayout 添加到布局中的 frameContainer 中
          frameContainer.addView(holderLayout, 0);

          // 添加 第一个包含 Fragment 的布局添加到 SpinMenu 中
          FrameLayout.LayoutParams pagerFrameParams = new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT);
          pagerLayout.setLayoutParams(pagerFrameParams);
          addView(pagerLayout);
        }

        // 显示标题
        if (hintStrList != null && !hintStrList.isEmpty() && i < hintStrList.size()) {
          tvHint = (TextView) smItemLayout.findViewWithTag(TAG_ITEM_HINT);
          tvHint.setText(hintStrList.get(i));
          tvHint.setTextSize(hintTextSize);
          tvHint.setTextColor(hintTextColor);
        }

        // 位于菜单中当前显示 Fragment 两边的 SMItemlayout 左右移动 TRAN_SKNEW_VALUE 个距离
        if (turnTableMenuLayout.getSelectedPosition() + 1 == i
            || (turnTableMenuLayout.isCyclic()
              && turnTableMenuLayout.getMenuItemCount() - i == turnTableMenuLayout.getSelectedPosition() + 1)) { // 右侧 ItemMenu
          smItemLayout.setTranslationX(TRAN_SKNEW_VALUE);
        } else if (turnTableMenuLayout.getSelectedPosition() - 1 == i
            || (turnTableMenuLayout.isCyclic()
              && turnTableMenuLayout.getMenuItemCount() - i == 1)) { // 左侧 ItemMenu
          smItemLayout.setTranslationX(-TRAN_SKNEW_VALUE);
        } else {
          smItemLayout.setTranslationX(0);
        }
      }
      turnTableMenuAnimator = new TurnTableMenuAnimator(this, turnTableMenuLayout, onSpinMenuStateChangeListener);
      init = false;
      openMenu();
    }
  }

  @Override
  public boolean dispatchTouchEvent(MotionEvent ev) {
    if (enableGesture) menuDetector.onTouchEvent(ev);
    return super.dispatchTouchEvent(ev);
  }

  @Override
  public boolean onTouchEvent(MotionEvent event) {
    if (enableGesture) {
      menuDetector.onTouchEvent(event);
      return true;
    } else {
      return super.onTouchEvent(event);
    }
  }

  /**
   * 根据手机的分辨率从 px(像素) 的单位转成为 sp
   * @param pxValue
   * @return
   */
  private int px2Sp(float pxValue) {
    final float fontScale = getContext().getResources().getDisplayMetrics().scaledDensity;
    return (int) (pxValue / fontScale + 0.5f);
  }

  private void log(String log) {
    Log.d(TAG, log);
  }

  public void setFragmentAdapter(PagerAdapter adapter) {
    if (pagerAdapter != null) {
      pagerAdapter.startUpdate(turnTableMenuLayout);
      for (int i = 0; i < adapter.getCount(); i++) {
        ViewGroup pager = (ViewGroup) turnTableMenuLayout.getChildAt(i).findViewWithTag(TAG_ITEM_PAGER);
        pagerAdapter.destroyItem(pager, i, pagerObjects.get(i));
      }
      pagerAdapter.finishUpdate(turnTableMenuLayout);
    }

    int pagerCount = adapter.getCount();
    if (pagerCount > turnTableMenuLayout.getMaxMenuItemCount())
      throw new RuntimeException(String.format("Fragment number can't be more than %d", turnTableMenuLayout.getMaxMenuItemCount()));

    pagerAdapter = adapter;

    SMItemLayout.LayoutParams itemLinLayParams = new SMItemLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
    LinearLayout.LayoutParams containerLinlayParams = new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT);
    FrameLayout.LayoutParams pagerFrameParams = new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT);
    LinearLayout.LayoutParams hintLinLayParams = new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
    hintLinLayParams.topMargin = HINT_TOP_MARGIN;
    pagerAdapter.startUpdate(turnTableMenuLayout);
    for (int i = 0; i < pagerCount; i++) {
      // 创建菜单父容器布局
      SMItemLayout smItemLayout = new SMItemLayout(getContext());
      smItemLayout.setId(i + 1);
      smItemLayout.setGravity(Gravity.CENTER);
      smItemLayout.setLayoutParams(itemLinLayParams);

      // 创建包裹FrameLayout
      FrameLayout frameContainer = new FrameLayout(getContext());
      frameContainer.setId(pagerCount + i + 1);
      frameContainer.setTag(TAG_ITEM_CONTAINER);
      frameContainer.setLayoutParams(containerLinlayParams);

      // 创建 Fragment 容器
      FrameLayout framePager = new FrameLayout(getContext());
      framePager.setId(pagerCount * 2 + i + 1);
      framePager.setTag(TAG_ITEM_PAGER);
      framePager.setLayoutParams(pagerFrameParams);
      Object object = pagerAdapter.instantiateItem(framePager, i);

      // 创建菜单标题 TextView
      TextView tvHint = new TextView(getContext());
      tvHint.setId(pagerCount * 3 + i + 1);
      tvHint.setTag(TAG_ITEM_HINT);
      tvHint.setLayoutParams(hintLinLayParams);

      frameContainer.addView(framePager);
      smItemLayout.addView(frameContainer);
      smItemLayout.addView(tvHint);
      turnTableMenuLayout.addView(smItemLayout);

      pagerObjects.add(object);
      smItemLayoutList.add(smItemLayout);
    }
    pagerAdapter.finishUpdate(turnTableMenuLayout);
  }

  public void openMenu() {
    if (menuState == MENU_STATE_CLOSED) {
      turnTableMenuAnimator.openMenuAnimator();
    }
  }

  public void closeMenu(SMItemLayout chooseItemLayout) {
    if (menuState == MENU_STATE_OPENED) {
      turnTableMenuAnimator.closeMenuAnimator(chooseItemLayout);
    }
  }

  public int getMenuState() {
    return menuState;
  }

  public void updateMenuState(int state) {
    menuState = state;
  }

  public void setEnableGesture(boolean enable) {
    enableGesture = enable;
  }

  public void setMenuItemScaleValue(float scaleValue) {
    scaleRatio = scaleValue;
  }

  public void setHintTextSize(int textSize) {
    hintTextSize = textSize;
  }

  public void setHintTextColor(int textColor) {
    hintTextColor = textColor;
  }

  public void setHintTextStrList(List<String> hintTextList) {
    hintStrList = hintTextList;
  }

  public void setOnSpinMenuStateChangeListener(OnSpinMenuStateChangeListener listener) {
    onSpinMenuStateChangeListener = listener;
  }

  public float getScaleRatio() {
    return scaleRatio;
  }
}

Github:SlidMenu

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

(0)

相关推荐

  • android底部菜单栏实现原理与代码

    上一个项目已经做完了,这周基本上没事,所以整理了下以前的项目,想把一些通用的部分封装起来,这样以后遇到相似的项目就不用重复发明轮子了,也节省了开发效率.今天把demo贴出来一是方便以后自己查询,二是希望同时也能帮到大家. 底部菜单栏很重要,我看了一下很多应用软件都是用了底部菜单栏做.我这里使用了tabhost做了一种通用的(就是可以像微信那样显示未读消息数量的,虽然之前也做过但是layout下的xml写的太臃肿,这里去掉了很多不必要的层,个人看起来还是不错的,所以贴出来方便以后使用). 先看一下

  • Android使用surfaceView自定义抽奖大转盘

    使用surfaceView自定义抽奖大转盘 话不多说,先上效果图 完整代码地址欢迎start 实现思路以及过程 1.首先了解SurfaceView的基本用法,它跟一般的View不太一样,采用的双缓存机制,可以在子线程中绘制View,不会因为绘制耗时而失去流畅性,这也是选择使用SurfaceView去自定义这个抽奖大转盘的原因,毕竟绘制这个转盘的盘块,奖项的图片和文字以及转动都是靠绘制出来的,是一个比较耗时的绘制过程. 2.使用SurfaceView的一般模板样式 一般会用到的成员变量 priva

  • Android中利用SurfaceView制作抽奖转盘的全流程攻略

    一.概述 今天给大家带来SurfaceView的一个实战案例,话说自定义View也是各种写,一直没有写过SurfaceView,这个玩意是什么东西?什么时候用比较好呢? 可以看到SurfaceView也是继承了View,但是我们并不需要去实现它的draw方法来绘制自己,为什么呢? 因为它和View有一个很大的区别,View在UI线程去更新自己:而SurfaceView则在一个子线程中去更新自己:这也显示出了它的优势,当制作游戏等需要不断刷新View时,因为是在子线程,避免了对UI线程的阻塞. 知

  • Android自定义view制作抽奖转盘

    本文实例为大家分享了Android自定义view制作抽奖转盘的具体代码,供大家参考,具体内容如下 效果图 TurntableActivity package com.bawei.myapplication.turntable; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.MotionEvent; import android.view.View; im

  • Android实现可点击的幸运大转盘

    之前的项目有一个幸运大转盘的功能,在网上找了很久,都没有合适的方法. 这是效果图,实现目标:十二星座的图片可点击切换选中效果,根据选择不同的星座,实现不同的 方法.之前网上的都是带有指针的,或者可点击改变效果,但是并不知道选择的到底是哪个,即虚拟选择. 实现该功能的主要代码如下: 1.自定义一个布局,存放图片,实现圆形布局. /** * * * CircleMenuLayout.java * * @author wuxiaosu * */ public class CircleMenuLayou

  • Android左右滑出菜单实例分析

    现在的Android应用,只要有一个什么新的创意,过不了多久,几乎所有的应用都带这个创意.这不,咱们公司最近的一个持续性的项目,想在首页加个从左滑动出来的菜单,我查阅网上资料,并自己摸索,实现了左.右两边都能滑出菜单,并且,左.右菜单中,都可以加ListView等这类需要解决GestureDetector冲突的问题(如在首页面中,含有ListView,上下滚动时,左右不动,相反,左右滑动菜单时,上下不动,听着头就大了吧!) 先上几张图,给大家瞧瞧,对整体有个了解:  一.首页布局: 复制代码 代

  • Android自定义View实现抽奖转盘

    本文实例为大家分享了Android自定义View实现抽奖转盘的具体代码,供大家参考,具体内容如下 public class LuckCircle extends SurfaceView implements SurfaceHolder.Callback,Runnable { private SurfaceHolder mHolder; private Canvas mCanvas; //用于绘制的线程 private Thread mThread; //线程开关的控制 private boole

  • 基于Android实现转盘按钮代码

    先给大家展示下效果图: package com.lixu.circlemenu; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.TextView; import android.widget.Toast; import com.lixu.circlemenu.view.CircleImageView; import com.lixu.ci

  • Android自定义View实现QQ运动积分转盘抽奖功能

    因为偶尔关注QQ运动, 看到QQ运动的积分抽奖界面比较有意思,所以就尝试用自定义View实现了下,原本想通过开发者选项查看下界面的一些信息,后来发现积分抽奖界面是在WebView中展示的,应该是在H5页面中用js代码实现的,暂时不去管它了. 这里的自定义View针对的是继承自View的情况,你可以将Canvas想象为画板, Paint为画笔,自定义View的过程和在画板上用画笔作画其实类似,想象在画板上作画的过程,你要画一个多大图形(对应View的测量 onMeasure方法),你要画什么样的图

  • Android实现抽奖转盘实例代码

    本文详述了android抽奖程序的实现方法,程序为一个抽奖大转盘代码,里面定义了很多图形方法和动画. 实现主要功能的SlyderView.java源代码如下: import android.app.Activity; import android.content.Context; import android.graphics.BlurMaskFilter; import android.graphics.Canvas; import android.graphics.Color; import

随机推荐