MY_GEEK_MYMENU--左右切換選單

飄過的小熊發表於2016-06-02

參考:《極客學院》

額,做完了來寫寫心得,準確地2說,原來自己的水平真的是一點都不夠,開始以為真的很簡單,講課內容看上去也是比較少,就那麼幾節,但是,真正做起來的時候才發現,真的是孤陋寡聞呀。很多東西都是第一次接觸,因為我看過的書也是沒有講過這些東西,都是靠不停的搜尋並做好筆記,自己領悟,很多地方自己現在也是沒有理解透徹,希望抓緊時間,抓緊時間奮鬥呀。第一次通過java寫主UI


總結

1.自定義控制元件

嗯,這裡涉及到一個自定義的view,我們使其繼承自FrameLayout,當然自定義view的步驟及方法肯定是不會少的,這個在第一行程式碼上只是簡單的介紹了自定義控制元件,講的自定義view也是自定義的listview,大有不同。談及到自定義view,就不可避免的複寫三大方法,分別是onMeasure(),onLayout(),onDraw(),當然也可以不進行復寫,但是一定要實現相應功能的方法。

2.觸控事件監聽分發

這個名詞也是第一次聽到。本實戰中使用的是ondispatchTouchEvent()這個方法,我們在這個方法中根據手指的滑動距離等等先判斷是什麼性質的事件(滑動還是觸控),假如是滑動,我們就要根據getScrollX()方法的值判斷是向左還是向右,並呼叫scroller中的方法進行滑動,當然我們選擇在滑動的過程中加一些特技,包括動畫呀,蒙版特效呀什麼的。

3.碎片新增

默默地就使用了碎片新增,大致也就是這麼個意思了,我們把所有的view都鋪好,然後通過新增的更換或者新增Fragment的方式來改變左右選單的內容。,。為什麼不直接在自定義view的時候就把內容寫好尼,方便呀,要是我想改變一下左選單的內容,難道要全部推倒重建?肯定是不可能的,因此,直接寫個碎片新增是極為方便的

實戰的原始碼

java

AtyMain

package com.example.test;

import android.os.Bundle;
import android.support.v4.app.FragmentActivity;

public class AtyMain extends FragmentActivity {
    private MainUI mainui;
    private LeftMenu leftMenu;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mainui = new MainUI(this);
        setContentView(mainui);
        leftMenu = new LeftMenu();
        getSupportFragmentManager().beginTransaction().add(MainUI.Left_ID, leftMenu).commit();
    }

}

LeftMenu的測試

package com.example.test;


import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;

public class LeftMenu extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        View v = inflater.inflate(R.layout.left, container, false);
        /**
         * 注意這裡也是在螢幕上的點選可能會與我們之前在判斷是滑動還是點選的事件有衝突,衝突的情況下就是無論我們如何點選按鈕都是不會觸發的,
         * 因此我們需要在view中對點選事件進行重新的分發
         */
        v.findViewById(R.id.button1).setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                System.out.println("hahh");
            }
        });
        return v;
    }

}

主UI

package com.example.test;

import android.content.Context;
import android.graphics.Color;
import android.graphics.Point;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import android.widget.Scroller;

/**
 * 總算是弄清除主線了,先判斷是點選還是滑動,判斷滑動後,再判斷是左右滑動還是上下滑動,並執行對應的邏輯
 * 
 * @author dell
 * 
 */
public class MainUI extends RelativeLayout { //自定義view就是這麼寫
    private Context context;
    private FrameLayout LeftMenu, MiddleMenu, RightMenu;
    private Scroller scroller;// 設定滑動

    private FrameLayout MiddleMask;// 實現蒙版的思路是,我們在中間蓋一層,然後一般情況設定的透明度透明的,然後透明度是隨著我我們滑動的距離改變而改變

    public static final int Left_ID = 0xaabbcc;//十六進位制
    public static final int Right_ID = 0xaaccbb;
    public static final int Middle_ID = 0xccaabb;

    // 自定義控制元件的構造方法標準形式
    public MainUI(Context context, AttributeSet attrs) {
        super(context, attrs);
        initview(context);
        // TODO Auto-generated constructor stub
    }

    /**
     * 為什麼要用這個構造方法? 通過實踐發現後面
     * 
     * @param context
     */
    public MainUI(Context context) {
        // TODO Auto-generated constructor stub
        super(context);
        initview(context);
    }

    /**
     * 接下來我們要建立子選單,它們需要context來承接上下文,準確的說破就是需要context作為引數
     * 
     * @param context
     */
    private void initview(Context context) {
        this.context = context;
        scroller = new Scroller(context, new DecelerateInterpolator());// 滑動也是需要例項化的,第二個引數是用於給動畫的,稱為渲染
        // 直接例項化一波
        LeftMenu = new FrameLayout(context);
        RightMenu = new FrameLayout(context);
        MiddleMenu = new FrameLayout(context);
        MiddleMask = new FrameLayout(context);
        // 為了視覺效果明顯設定一些背景
        LeftMenu.setBackgroundColor(Color.RED);
        MiddleMenu.setBackgroundColor(Color.GREEN);
        RightMenu.setBackgroundColor(Color.RED);

        MiddleMask.setBackgroundColor(0x88000000);// 原來設定顏色還可以這樣設定,66666,這裡設定的最初的顏色,這裡是灰色,但是不是很深

        // 設定對應的ID
        LeftMenu.setId(Left_ID);
        MiddleMenu.setId(Middle_ID);
        RightMenu.setId(Right_ID);
        // 接下來需要將這三個區域全部填充到一個view當中,事實上這個view就是承載這三個區域的最外層的Relativelayout
        addView(LeftMenu);
        addView(RightMenu);
        addView(MiddleMenu);
        addView(MiddleMask);// 它是與其他選單一起加進去的,因此也需要在寬度和高度上進行設定
        MiddleMask.setAlpha(0);// 最開始肯定是透明的
    }

    /**
     * 我們在這個方法中監聽事件的變化,並設定透明度
     */

    @Override
    public void scrollTo(int x, int y) {//根據滑動的距離變化而變化,因此要想在滑動的時候有什麼動作,就可以放在這個方法中,後面執行這個方法就可以了
        // TODO Auto-generated method stub
        super.scrollTo(x, y);
        int curX=Math.abs(getScrollX());//根據這個變化而變化
        float scale=curX/(float)LeftMenu.getMeasuredWidth();//設定比例
        MiddleMask.setAlpha(scale);
    }

    /**
     * 直接新增進去是沒有辦法顯示的,我們需要測量一下寬度和高度,這個寬度和高度就是我們當前螢幕準確的寬度和高度
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // TODO Auto-generated method stub
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        MiddleMenu.measure(widthMeasureSpec, heightMeasureSpec);// 中間部分最好設定,因為我們是要中間部分充滿螢幕,左右選單高度相同,寬度不同
        MiddleMask.measure(widthMeasureSpec, heightMeasureSpec);// 蒙版的寬高和中間的選單一樣
        int realWidth = MeasureSpec.getSize(widthMeasureSpec);// 獲取到整體螢幕的寬度
        int tempWidthMeasure = MeasureSpec.makeMeasureSpec(
                (int) (realWidth * 0.8), MeasureSpec.EXACTLY);// 獲取左右選單的寬,注意引數型別,本方法的引數全是int型別
        // 而我們的0.8是浮點數,我們使用EACTLY模式,
        RightMenu.measure(tempWidthMeasure, heightMeasureSpec);// 設定左右選單的寬度和高度,可以根據專案的需求設定不同的左右選單寬度
        LeftMenu.measure(tempWidthMeasure, heightMeasureSpec);
    }

    /**
     * 通過onLayout進行填充
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        // TODO Auto-generated method stub
        super.onLayout(changed, l, t, r, b);
        // 先填中間部分
        // 三個塊在填充時按照一塊來看,因此在加入左選單後,左上角的座標位置改變而右下角的座標位置不變,最後加入右選單時左上角不變而右下角改變
        MiddleMenu.layout(l, t, r, b);// 基本充滿螢幕,座標

        MiddleMask.layout(l, t, r, b);// 蒙版的位置也和中間選單差不多

        LeftMenu.layout(l - LeftMenu.getMeasuredWidth(), t, r, b);// 只能說老師也是沒有弄清楚,這裡還是要使用view的座標來回答問題,就是四個象限那幅圖
        // 也可以使用,l-tempWidthMeasure,
        RightMenu.layout(
                l + MiddleMenu.getMeasuredWidth(),
                t,
                l + MiddleMenu.getMeasuredWidth()
                        + RightMenu.getMeasuredWidth(), b);
    }

    private boolean isWhat;// 用於標記是什麼事件,滑動還是點選
    private boolean isLeftOrRight;// 用於標記是否為左右滑動

    /**
     * 下面就寫一個監聽事件來判斷是向左還是向右滑動,自己寫事件分發,後面理解的事件分發的意思應該也就是不同的事件執行不同的邏輯
     * 不過本實戰是通過兩個if語句,先對事件進行了判斷,然後根據事件作出相應的動作 
     * 
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        /**
         * 我們要判斷是上下事件還是左右事件,上下事件是針對上下滑動的,左右事件是針對我們的左右選單滑動的,還有一個是點選事件的處理
         */
        // TODO Auto-generated method stub
        if (!isWhat) {
            getEventType(ev);// 建立一個方法對事件進行判斷
            return true;
        }
        // 判斷左右滑動
        if (isLeftOrRight) {// 若是左右滑動
            switch (ev.getActionMasked()) {
            case MotionEvent.ACTION_MOVE:// 這個case是一點都沒有防備,是一點都不知道原因,也解釋不出來
                int curScrollX = getScrollX();// 定義一個滾動的距離
                int dis_x = (int) (ev.getX() - point.x);// 獲取手指放下以及滑動的距離,就是滑動的距離
                int expectx = -dis_x + curScrollX;
                int finalX = 0;
                if (expectx < 0) {// 向左滑動
                    finalX = Math.max(expectx, -LeftMenu.getMeasuredWidth());// 兩個值都是負的,取絕對值較小的。
                } else {// 向右滑動
                    finalX = Math.min(expectx, RightMenu.getMeasuredWidth());// 兩個值都是正的,取絕對值較大的。
                }
                scrollTo(finalX, 0);
                point.x = (int) ev.getX();
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                curScrollX = getScrollX();// 這個值要是像右滑就是負值,向左滑就是正值,getScrollx()方法返回的就是喜歡的型別
                if (Math.abs(curScrollX) > LeftMenu.getMeasuredWidth() >> 1) {// 右移一位就是除以二,我們設定的情形是滑動距離超過左選單一般的時候,允許滑動
                    if (curScrollX < 0) {// 手指向右動,出現左選單,
                        scroller.startScroll(curScrollX, 0,
                                -LeftMenu.getMeasuredWidth() - curScrollX, 0,
                                200);// 開始滑動,起始座標為手指接觸螢幕滑動結束的位置,結束位置設定為全部顯示,y不用管,因為本身就不用變
                    } else {
                        scroller.startScroll(curScrollX, 0,
                                RightMenu.getMeasuredWidth() - curScrollX, 0,
                                200);// 兩百是我們給定的動畫執行時間,200ms
                    }
                } else {
                    scroller.startScroll(curScrollX, 0, -curScrollX, 0);// 滑動距離沒有超過標準就返回到原位置
                }
                invalidate();//重繪
                isLeftOrRight = false;//再次初始化
                isWhat = false;
                break;

            default:
                break;
            }
        } else {
            // 若不是左右滑動,就是上下了
            switch (ev.getActionMasked()) {
            case MotionEvent.ACTION_UP:
                isLeftOrRight = false;
                isWhat = false;
                break;

            default:
                break;
            }
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public void computeScroll() {
        // TODO Auto-generated method stub
        super.computeScroll();// 這個回撥方法一定要重寫不然不會滑動
        if (!scroller.computeScrollOffset()) {
            return;
        }
        int tempX = scroller.getCurrX();// 定義一個我們滑動的值,這個滑動的值就是我們滑動的距離
        scrollTo(tempX, 0);
    }

    private Point point = new Point();// 設定一個點,根據點獲取到當前滑動的距離,根據滑動的距離判斷是滑動還是點選
    private static final int TEST_DIS = 20;// 定義一個做比較的值,如果大於20就是滑動了

    private void getEventType(MotionEvent ev) {
        // TODO Auto-generated method stub
        // 基本的事件也就這麼多(三個)
        switch (ev.getActionMasked()) {
        case MotionEvent.ACTION_DOWN:// 滑鼠 按下的時候做處理
            point.x = (int) ev.getX();// 每次都要獲取點的座標,getX()方法是返回float型別,因此我們要強製為int型別
            point.y = (int) ev.getY();
            super.dispatchTouchEvent(ev);// 具體的處理點選事件的邏輯
            break;

        case MotionEvent.ACTION_MOVE:// 滑鼠移動的時候做處理
            int dX = Math.abs((int) ev.getX() - point.x);// 只看絕對值,左右滑動不介意
            int dY = (int) Math.abs(ev.getY() - point.y);
            if (dX >= TEST_DIS && dX > dY) {// 前半句代表是滑動,後半句代表是左右,代表是左右滑動
                isLeftOrRight = true;// 左右滑動
                isWhat = true;// 是滑動
                point.x = (int) ev.getX();// 為了每次滑動過後,接下來能夠繼續滑動,每次都要獲取點的座標,getX()方法是返回float型別,因此我們要強製為int型別
                point.y = (int) ev.getY();
            } else if (dY >= TEST_DIS && dY > dX) {// 代表上下滑動
                isLeftOrRight = false;// 不是左右滑動
                isWhat = true;// 是滑動
                point.x = (int) ev.getX();// 每次都要獲取點的座標,getX()方法是返回float型別,因此我們要強製為int型別
                point.y = (int) ev.getY();
            }
            break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:// 處理手指滑到邊緣的情況
            super.dispatchTouchEvent(ev);// 點選事件不需要我們自己處理,我們直接拋給系統處理就ok了
            isLeftOrRight = false;
            isWhat = false;
            break;
        default:
            break;
        }

    }
}

leftMenu選單的碎片UI

<?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" >

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button" />

</LinearLayout>

相關文章