国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統 > Android > 正文

Android自定義view實現圓形與半圓形菜單

2019-12-12 04:51:11
字體:
來源:轉載
供稿:網友

前不久看到鴻洋大大的圓形菜單,就想開始模仿,因為實在是太酷了,然后自己根據別人(zw哥)給我講的一些思路、一些分析,就開始改造自己的圓形菜單了。

文章結構:1.功能介紹以及展示;2.部分代碼講解;3.大致可以實現的UI效果展示講解。4.源碼附送。

一、功能介紹以及展示

 

第一個展示是本控件的原樣。但是我們可以使用很多技巧去達到我們的商業UI效果嘛。

這里給出的是本博客作品demo的展示圖以及第三點的聯動展示,可見是一圓型菜單,相較于鴻洋大大的那個圓形菜單多了一些需求:

1.到時候展示只需要半圓的轉盤。

2.在規定的角度不能讓他們自動旋轉(涉及延伸的一些數學計算,一會重點講解)。

3.要綁定fragment。

4.一個緩沖角度,即我們將要固定幾個位置,而不是任意位置。我們要設計一個可能的角度去自動幫他選擇。

二、代碼講解

結合實際使用的方式來講解。分為:1.調用方式;2.此控件onMeasure方法;3.onLayout方法的作用;4.此控件事件機制dispatchTouchEvent的使用;5.數學計算―一個緩沖角度。

(1)調用方式 :(代碼為展示區下方的效果代碼)

//采用的是聯動,使用Fragment管理器FragmentTransaction去實現fragment管理package com.fuzhucheng.circlemenu;import android.os.Bundle;import android.support.v4.app.FragmentTransaction;import android.support.v7.app.AppCompatActivity;import android.view.KeyEvent;import android.view.View;import android.widget.Toast;public class MainActivity extends AppCompatActivity { private UpCircleMenuLayout myCircleMenuLayout; //四個fragment頁面 private HomepageFragment homepageFragment; private SettingFragment settingFragment; private HistoryFragment historyFragment; private FourthFragment fourthFragment; private FifthFragment fifthFragment; private String[] mItemTexts = new String[]{"安全中心 ", "特色服務", "投資理財",   "轉賬匯款", "我的賬戶", "安全中心", "特色服務", "投資理財", "轉賬匯款", "我的賬戶"}; private int[] mItemImgs = new int[]{R.drawable.home_mbank_1_normal,   R.drawable.home_mbank_2_normal, R.drawable.home_mbank_3_normal,   R.drawable.home_mbank_4_normal, R.drawable.home_mbank_5_normal,   R.drawable.home_mbank_1_normal, R.drawable.home_mbank_2_normal,   R.drawable.home_mbank_3_normal, R.drawable.home_mbank_4_normal,   R.drawable.home_mbank_5_normal}; @Override protected void onCreate(Bundle savedInstanceState) {  super.onCreate(savedInstanceState);  setContentView(R.layout.activity_main);  //第一次初始化首頁默認顯示第一個fragment  initFragment1();  myCircleMenuLayout = (UpCircleMenuLayout) findViewById(R.id.id_mymenulayout);  myCircleMenuLayout.setMenuItemIconsAndTexts(mItemImgs);//一句設置圖片  myCircleMenuLayout.setOnMenuItemClickListener(new UpCircleMenuLayout.OnMenuItemClickListener() {   @Override   public void itemClick(int pos) {    Toast.makeText(MainActivity.this, mItemTexts[pos],      Toast.LENGTH_SHORT).show();    switch (pos) {     case 0:      initFragment1();      setTitle("安全中心");      break;     case 1:      initFragment2();      setTitle("特色服務");      break;     case 2:      initFragment3();      setTitle("投資理財");      break;     case 3:      initFragment4();      setTitle("轉賬匯款");      break;     case 4:      initFragment5();      setTitle("我的賬戶");      break;     case 5:      initFragment1();      setTitle("安全中心");      break;     case 6:      initFragment2();      setTitle("特色服務");      break;     case 7:      initFragment3();      setTitle("投資理財");      break;     case 8:      initFragment4();      setTitle("轉賬匯款");      break;     case 9:      initFragment5();      setTitle("我的賬戶");      break;    }   }   @Override   public void itemCenterClick(View view) {    Toast.makeText(MainActivity.this,      "you can do something just like ccb ",      Toast.LENGTH_SHORT).show();   }  }); } //顯示第一個fragment private void initFragment1(){  //開啟事務,fragment的控制是由事務來實現的  homepageFragment = new HomepageFragment();  FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();  transaction.replace(R.id.fragment_tv,homepageFragment);  transaction.addToBackStack(null);  transaction.commit(); } //顯示第二個fragment private void initFragment2(){  //開啟事務,fragment的控制是由事務來實現的  settingFragment = new SettingFragment();  FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();  transaction.replace(R.id.fragment_tv,settingFragment);  transaction.addToBackStack(null);  transaction.commit(); } private void initFragment3(){  //開啟事務,fragment的控制是由事務來實現的  historyFragment = new HistoryFragment();  FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();  transaction.replace(R.id.fragment_tv,historyFragment);  transaction.addToBackStack(null);  transaction.commit(); } private void initFragment4(){  //開啟事務,fragment的控制是由事務來實現的  fourthFragment = new FourthFragment();  FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();  transaction.replace(R.id.fragment_tv,fourthFragment);  transaction.addToBackStack(null);  transaction.commit(); } private void initFragment5(){  //開啟事務,fragment的控制是由事務來實現的  fifthFragment = new FifthFragment();  FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();  transaction.replace(R.id.fragment_tv,fifthFragment);  transaction.addToBackStack(null);  transaction.commit(); } public boolean onKeyDown(int keyCode, KeyEvent event) {  if (keyCode == KeyEvent.KEYCODE_BACK    && event.getRepeatCount() == 0) {   finish();   return true;  }  return super.onKeyDown(keyCode, event); }}

(2)此控件onMeasure方法講解:重點講解迭代測量

/**  * 設置布局的寬高,并策略menu item寬高  */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  int resWidth = 0;  int resHeight = 0;  double startAngle = mStartAngle;  double angle = 360 / 10; //我們傳入了10個孩子  /**   * 根據傳入的參數,分別獲取測量模式和測量值   */  int width = MeasureSpec.getSize(widthMeasureSpec);  int widthMode = MeasureSpec.getMode(widthMeasureSpec);  int height = MeasureSpec.getSize(heightMeasureSpec);  int heightMode = MeasureSpec.getMode(heightMeasureSpec);  /**   * 如果寬或者高的測量模式非精確值   */  if (widthMode != MeasureSpec.EXACTLY    || heightMode != MeasureSpec.EXACTLY) {   // 主要設置為背景圖的高度   resWidth = getDefaultWidth();   resHeight = (int) (resWidth * DEFAULT_BANNER_HEIGTH /     DEFAULT_BANNER_WIDTH);  } else {   // 如果都設置為精確值,則直接取小值;   resWidth = resHeight = Math.min(width, height);  }  setMeasuredDimension(resWidth, resHeight);  // 獲得直徑  mRadius = Math.max(getMeasuredWidth(), getMeasuredHeight());  // menu item數量  final int count = getChildCount();  // menu item尺寸  int childSize;  // menu item測量模式  int childMode = MeasureSpec.EXACTLY;  // 迭代測量:根據孩子的數量進行遍歷,為每一個孩子測量大小,設置監聽回調。  for (int i = 0; i < count; i++) {   final View child = getChildAt(i);   startAngle = startAngle % 360;   if (startAngle > 269 && startAngle < 271 && isTouchUp) {    mOnMenuItemClickListener.itemClick(i); //設置監聽回調。    mCurrentPosition = i; //本次使用mCurrentPosition,只是把他作為一個temp變量,可以有更多的使用,比如動態設置每個孩子相隔的角度    childSize = DensityUtil.dip2px(getContext(), RADIO_TOP_CHILD_DIMENSION);//設置大小   } else {    childSize = DensityUtil.dip2px(getContext(), RADIO_DEFAULT_CHILD_DIMENSION);//設置大小   }   if (child.getVisibility() == GONE) {    continue;   }   // 計算menu item的尺寸;以及和設置好的模式,去對item進行測量   int makeMeasureSpec = -1;   makeMeasureSpec = MeasureSpec.makeMeasureSpec(childSize,     childMode);   child.measure(makeMeasureSpec, makeMeasureSpec);   startAngle += angle;  }//item容器內邊距  mPadding = DensityUtil.dip2px(getContext(), RADIO_MARGIN_LAYOUT); }

onMeasure深入:View在屏幕上顯示出來要先經過measure(計算)和layout(布局)。這方法作用就是計算出自定義View的寬度和高度。這個計算的過程參照父布局給出的大小,以及自己特點算出結果 。當然,還有相關的尺寸測量模式。此處奉上一篇好博文:onMeasure理解。此外,我還在這方法里作為監聽回調的設置??!而為控件設置圖片可以直接使用我們下面設計的方法:setMenuItemIconsAndTexts一句收工。

(3)onLayout方法的講解:(此處的圓的數學計算布置圖標圍繞圓位置可見鴻洋大大的推薦,講得很清楚,當然我下面也會略微講解下)

/**  * 設置menu item的位置  */ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) {  int layoutRadius = mRadius;  // Laying out the child views  final int childCount = getChildCount();  int left, top;  // menu item 的尺寸  int cWidth;  // 根據menu item的個數,計算角度  float angleDelay = 360 / 10;  // 遍歷去設置menuitem的位置  for (int i = 0; i < childCount; i++) {   final View child = getChildAt(i);    //根據孩子遍歷,設置中間頂部那個的大小以及其他圖片大小。   if (mStartAngle > 269 && mStartAngle < 271 && isTouchUp) {    cWidth = DensityUtil.dip2px(getContext(), RADIO_TOP_CHILD_DIMENSION);    child.setSelected(true);   } else {    cWidth = DensityUtil.dip2px(getContext(), RADIO_DEFAULT_CHILD_DIMENSION);    child.setSelected(false);   }   if (child.getVisibility() == GONE) {    continue;   }    //大于360就取余歸于小于360度   mStartAngle = mStartAngle % 360;   float tmp = 0;   //計算圖片布置的中心點的圓半徑。就是tmp   tmp = layoutRadius / 2f - cWidth / 2 - mPadding;   // tmp cosa 即menu item中心點的橫坐標。計算的是item的位置,是計算位置!!!   left = layoutRadius     / 2     + (int) Math.round(tmp     * Math.cos(Math.toRadians(mStartAngle)) - 1 / 2f     * cWidth) + DensityUtil     .dip2px(getContext(), 1);   // tmp sina 即menu item的縱坐標   top = layoutRadius     / 2     + (int) Math.round(tmp     * Math.sin(Math.toRadians(mStartAngle)) - 1 / 2f * cWidth) + DensityUtil     .dip2px(getContext(), 8);   //接著當然是布置孩子的位置啦,就是根據小圓的來布置的   child.layout(left, top, left + cWidth, top + cWidth);   // 疊加尺寸   mStartAngle += angleDelay;  } }

給出鴻洋大大的計算小圓的思路圖:

(4)此控件事件機制dispatchTouchEvent的使用:

//dispatchTouchEvent是處理觸摸事件分發,事件(多數情況)是從Activity的dispatchTouchEvent開始的。執行super.dispatchTouchEvent(ev),事件向下分發。 //onTouchEvent是View中提供的方法,ViewGroup也有這個方法,view中不提供onInterceptTouchEvent。view中默認返回true,表示消費了這個事件。 @Override public boolean dispatchTouchEvent(MotionEvent event) {  float x = event.getX();  float y = event.getY();  getParent().requestDisallowInterceptTouchEvent(true);  switch (event.getAction()) {   case MotionEvent.ACTION_DOWN:   //直接就是獲取x,y值了,還有一個DownTime(附送)    mLastX = x;    mLastY = y;    mDownTime = System.currentTimeMillis();    mTmpAngle = 0;    break;   case MotionEvent.ACTION_MOVE:    isTouchUp = false; //注意isTouchUp 這個標記量?。?!    /**     * 獲得開始的角度     */    float start = getAngle(mLastX, mLastY);    /**     * 獲得當前的角度     */    float end = getAngle(x, y);    // 如果是一、四象限,則直接end-start,角度值都是正值    if (getQuadrant(x, y) == 1 || getQuadrant(x, y) == 4) {     mStartAngle += end - start;     mTmpAngle += end - start;//按下到抬起時旋轉的角度    } else    // 二、三象限,色角度值是負值    {     mStartAngle += start - end;     mTmpAngle += start - end;    }    // 重新布局    if (mTmpAngle != 0) {     requestLayout();    }    mLastX = x;    mLastY = y;    break;   case MotionEvent.ACTION_UP:   //當手指UP啦,就是關鍵啦,一個緩沖角度,即我們將要固定幾個位置,而不是任意位置。我們要設計一個可能的角度去自動幫他選擇。    backOrPre();    break;  }  return super.dispatchTouchEvent(event); }

MotionEvent事件機制:(此控件我只用了三個)主要的事件類型有:ACTION_DOWN: 表示用戶開始觸摸。ACTION_MOVE: 表示用戶在移動(手指或者其他)。ACTION_UP:表示用戶抬起了手指。

(5)數學計算―一個緩沖角度。

 private void backOrPre() {  //緩沖的角度。即我們將要固定幾個位置,而不是任意位置。我們要設計一個可能的角度去自動幫他選擇。  isTouchUp = true;  float angleDelay = 360 / 10;    //這個是每個圖形相隔的角度  //我們本來的上半圓的圖片角度應該是:18,54,90,126,162。所以我們這里是:先讓當前角度把初始的18度減去再取余每個圖形相隔角度。得到的是什么呢?就是一個圖片本來應該在的那堆角度。所以如果是就直接return了。  if ((mStartAngle-18)%angleDelay==0){   return;  }  float angle = (float)((mStartAngle-18)%36);     //angle就是那個不是18度開始布局,然后是36度的整數的多出來的部分角度  //以下就是我們做的緩沖角度處理啦,如果多出來的部分角度大于圖片相隔角度的一半就往前進一個,如果小于則往后退一個。  if (angleDelay/2 > angle){   mStartAngle -= angle;  }else if (angleDelay/2<angle){   mStartAngle = mStartAngle - angle + angleDelay;   //mStartAngle就是當前角度啦,取余36度就是多出來的角度,拿這個多出來的角度去數據處理。  }  //然后重新布局onlayout  requestLayout(); }

至于其他小的方法詳情,可見源代碼,有詳細解釋。

源碼傳送門:github地址:Android-自定義view之圓形與“半圓形”菜單 喜歡的可以star或fork啦,謝謝!

好了,Android-自定義view之圓形與“半圓形”菜單講完了。本博客是經過仔細研究鴻洋大大的圓形菜單博客的,并在這里做出進一步拓展以及寫出自己的理解。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 长武县| 吉安县| 房山区| 汝阳县| 临清市| 普宁市| 民和| 清涧县| 平陆县| 巴南区| 宁乡县| 水富县| 尉犁县| 巴彦淖尔市| 浦江县| 迁西县| 大关县| 鹰潭市| 武威市| 奇台县| 常州市| 阿克| 珠海市| 井陉县| 来凤县| 德令哈市| 漳平市| 中方县| 江门市| 堆龙德庆县| 顺昌县| 江源县| 平乡县| 中牟县| 洪雅县| 乌兰浩特市| 临澧县| 阿勒泰市| 武威市| 巨野县| 睢宁县|