SelectLayout一個(gè)選中放大的容器

SelectLayout

SelectLayout一個(gè)選中放大的容器。

先看看效果吧

效果圖

整體思路

從設(shè)計(jì)那里得知要這么一個(gè)效果時(shí)候,第一反應(yīng)就是用ViewPager或者畫(huà)廊等去實(shí)現(xiàn),也找了對(duì)應(yīng)的資料,但是最終效果都不是很滿意,給兩個(gè)ViewPager實(shí)現(xiàn)類(lèi)似效果的鏈接仿土巴兔ViewPagerCards。
最終還是通過(guò)最基本的平移動(dòng)畫(huà)和縮放動(dòng)畫(huà)效果實(shí)現(xiàn)的,自定義一個(gè)繼承自RelativeLayout的Layout,至于為什么是RelativeLayout后面說(shuō)到哈。通過(guò)放大中間的一個(gè)View,然后每次點(diǎn)擊或者滑動(dòng)出發(fā)切換時(shí)候,通過(guò)動(dòng)畫(huà)平移和縮放子View。接下來(lái)一起來(lái)看看代碼。

自定義屬性

<declare-styleable name="SelectLayout">
        <!--大小球中間間隔和小球的縮放比例-->
        <attr name="spaceScale" format="float"/>
        <!--大小球的縮放比例-->
        <attr name="scale" format="float"/>
        <!--是否相應(yīng)滑動(dòng)事件-->
        <attr name="isMoveScrollable" format="boolean"/>
        <!--動(dòng)畫(huà)時(shí)長(zhǎng)-->
        <attr name="animTime" format="integer"/>
 </declare-styleable>

這里提供了四各自定義屬性,注釋都寫(xiě)得很清楚了。
其中spaceScale這個(gè)值,大小View中間的間隔占小View的比例,如小View寬80dp,間距想設(shè)置為20dp,那么這個(gè)值應(yīng)該為0.25;其中spaceScale這個(gè)值,是view放大的比例,如80dp->100dp,那么這個(gè)值就是(100-80)/80=0.25;

下面是自定義值的獲取,記得后面要調(diào)用recycle()

 //自定義屬性的獲取
        TypedArray ta=context.obtainStyledAttributes(attrs, R.styleable.SelectLayout,defStyleAttr,0);
        isMoveScrollable=ta.getBoolean(R.styleable.SelectLayout_isMoveScrollable,true);
        mScale=ta.getFloat(R.styleable.SelectLayout_scale,0.3f);
        mSpaceScale=ta.getFloat(R.styleable.SelectLayout_spaceScale,0.2f);
        mAnimTime=ta.getInt(R.styleable.SelectLayout_spaceScale,500);
        ta.recycle();

子View相關(guān)屬性值獲取

接下來(lái)就需要獲取子View以及他們的寬高,在onFinishInflate()方法里面可以獲取到View,這個(gè)方法表示著ViewGroup已經(jīng)完成了布局:

@Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        initView();
    }
    /**
     * 初始化View
     */
    public void initView() {
        viewLeft=findViewById(R.id.viewLeft);
        viewCenter=findViewById(R.id.viewCenter);
        viewRight=findViewById(R.id.viewRight);

        viewLeft.setOnClickListener(this);
        viewCenter.setOnClickListener(this);
        viewRight.setOnClickListener(this);
    }

然后再onSizeChanged()方法里面可以獲取到子View的一些寬高屬性:

@Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        initSize();
    }

    /**
     * 初始化View大小 縮放比例
     */
    private void initSize() {
        //兩邊小的寬度
        mMinWidth=viewLeft.getMeasuredWidth();
        //按照比例設(shè)置間距
        mSpaceWidth=mMinWidth*mSpaceScale;
        //中間放大的寬度
        mMaxWidth=mMinWidth*(1+mScale);
        //默認(rèn)中間一個(gè)View放大
        viewCenter.setScaleX(1+mScale);
        viewCenter.setScaleY(1+mScale);

        if(mMaxWidth>getMeasuredHeight()){//縮放法高度大于Layout高度  重新設(shè)置高度 自適應(yīng)
            setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,(int)mMaxWidth));
        }

        //設(shè)置中間布局margin
        LayoutParams params= (LayoutParams) viewCenter.getLayoutParams();
        params.leftMargin= (int) (mSpaceWidth+mMinWidth*mScale/2);//間距加上放大的邊距
        params.rightMargin= (int) (mSpaceWidth+mMinWidth*mScale/2);
        viewCenter.setLayoutParams(params);
    }

這里要注意,調(diào)用的是getMeasuredWidth(),而不是getWidth(),getWidth()此時(shí)還是0,只有當(dāng)已經(jīng)顯示在屏幕上面才會(huì)有值的。

動(dòng)畫(huà)實(shí)現(xiàn)基本效果

其實(shí)動(dòng)畫(huà)很簡(jiǎn)單,這里面主要用到了屬性動(dòng)畫(huà),完成平移以及縮放。先來(lái)分析從最初的狀態(tài),點(diǎn)擊左邊時(shí)候的整個(gè)過(guò)程:

狀態(tài)切換

左是從左往右平移一個(gè) 左邊View一半+間隔+放大后的View寬度的一半,然后放大mScale;
中是從左往右平移一個(gè) 左邊View一半+間隔+放大后的View寬度的一半,然后縮小mScale;
又是從左往左平移一個(gè) (左邊View一半+間隔+放大后的View寬度的一半)*2;

if(currentSelect== CurrentSelect.center) {//左邊到中間
                //左邊View平移 方大
                ObjectAnimator animator1 = ObjectAnimator.ofFloat(viewLeft, "translationX", 0, mMinWidth/2+mMaxWidth/2+mSpaceWidth);
                ObjectAnimator animator2 = ObjectAnimator.ofFloat(viewLeft, "scaleX", 1, 1+mScale);
                ObjectAnimator animator3 = ObjectAnimator.ofFloat(viewLeft, "scaleY", 1, 1+mScale);
                //中間View 右移 縮小
                ObjectAnimator animator4 = ObjectAnimator.ofFloat(viewCenter, "translationX",0, mMinWidth/2+mMaxWidth/2+mSpaceWidth);
                ObjectAnimator animator5 = ObjectAnimator.ofFloat(viewCenter, "scaleX", 1+mScale,1f);
                ObjectAnimator animator6 = ObjectAnimator.ofFloat(viewCenter, "scaleY", 1+mScale,1f);
                //右邊View 左移
                ObjectAnimator animator7 = ObjectAnimator.ofFloat(viewRight, "translationX",0, -(mMinWidth+mMaxWidth+mSpaceWidth*2));

                playAnim(CurrentSelect.left,animator1, animator2, animator3,animator4, animator5, animator6,animator7);
            }

其他情況都是類(lèi)似的邏輯,就不贅述了。需要注意的是,動(dòng)畫(huà)的起始位置和結(jié)束位置都是相對(duì)于最開(kāi)始的布局的,動(dòng)畫(huà)只是改變view的表現(xiàn)形式,并不會(huì)改變view真正的位置。

滑動(dòng)切換

點(diǎn)擊按鈕切換基本已經(jīng)實(shí)現(xiàn)了,下面就來(lái)看看怎么實(shí)現(xiàn)滑動(dòng)實(shí)現(xiàn)。自然是獲取到滑動(dòng)到距離,是否超過(guò)一定距離,判斷是左滑還是右滑,然后切換選中狀態(tài)。由于涉及到子View的點(diǎn)擊事件,所以需要用到事件分發(fā):

float downX=0;
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        if(isMoveScrollable) {//響應(yīng)滑動(dòng)事件  事件分發(fā)
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    downX = event.getX();
                    break;
                case MotionEvent.ACTION_MOVE:
                    float moveX = event.getX();
                    if (Math.abs(moveX - downX) > 50 && moveX > downX) {//右滑
                        downX = moveX;
                        if (currentSelect == CurrentSelect.left) {
                            onItemClick(false,3);
                        } else if (currentSelect == CurrentSelect.center) {
                            onItemClick(false,1);
                        } else {
                            onItemClick(false,2);
                        }
                        return true;
                    } else if (Math.abs(moveX - downX) > 50 && moveX < downX) {//左滑
                        downX = moveX;
                        if (currentSelect == CurrentSelect.left) {
                            onItemClick(false,2);
                        } else if (currentSelect == CurrentSelect.center) {
                            onItemClick(false,3);
                        } else {
                            onItemClick(false,1);
                        }
                        return true;
                    }
                    break;
            }
        }
        return super.dispatchTouchEvent(event);
    }

我們這里只需要關(guān)心X軸值的變化就行,在move里面判斷是左滑或者右滑后,切換選中view,并且返回true表示本身消費(fèi)了這次事件,不會(huì)再往下分發(fā)該事件。

View的完善

外部在使用Layout的時(shí)候,肯定需要知道我們當(dāng)前選中的View,以及狀態(tài)切換的監(jiān)聽(tīng)。

/**
     * 選中狀態(tài)更改回調(diào)
     */
    public interface OnSelectChangedListener{
        /**
         * 選中項(xiàng)改變
         * @param current
         */
        public void onSelectChange(CurrentSelect current);

        /**
         * 選中項(xiàng)被點(diǎn)擊
         */
        public void onSelectClick();
    }

還有一些其他屬性的設(shè)置

/**
     * 設(shè)置動(dòng)畫(huà)時(shí)間
     * @param animTime
     */
    public void setAnimTime(int animTime) {
        mAnimTime = animTime;
    }

    /**
     * 是否能夠滑動(dòng)
     * @param moveScrollable
     */
    public void setMoveScrollable(boolean moveScrollable) {
        isMoveScrollable = moveScrollable;
    }

還有一點(diǎn)就是開(kāi)頭說(shuō)的為什么用的是RelativeLayout,其實(shí)最開(kāi)始我用的是LinearLayout。功能都已經(jīng)實(shí)現(xiàn)了,但是后面發(fā)現(xiàn)動(dòng)畫(huà)的時(shí)候,左邊的始終是在最下面一層,右邊的始終在最上面一層,體驗(yàn)效果非常不好,沒(méi)有循環(huán)的效果。經(jīng)過(guò)仔細(xì)觀察,問(wèn)題是因?yàn)樽覸iew的ViewGroup的層級(jí)關(guān)系問(wèn)題,問(wèn)題來(lái)了,那怎么才可以更改子View的層級(jí)關(guān)系么。經(jīng)過(guò)一番查詢(xún),view.bringToFront(),這個(gè)方法正是我們要找的。但是在LinearLayout中嘗試了并沒(méi)有效果,于是我就只能用RelativeLayout了,哈哈。要是有其他更好的方法請(qǐng)指教。

viewRight.bringToFront();
viewCenter.bringToFront();
viewLeft.bringToFront();

說(shuō)明

這個(gè)Layout里面獲取子View是根據(jù)子View的id獲取的,所以務(wù)必將三個(gè)字View的Id分別設(shè)置為viewLeft、viewCenter、viewRight。好吧,我承認(rèn)這是一個(gè)很操蛋的地方,如果不這樣,那么就通過(guò)getChildAt()索引獲取,那樣也有一定的限制。有需要就自行修改吧。我只是做個(gè)筆記

最后附上源碼地址:https://github.com/HanJinLiang/SelectLayout

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,082評(píng)論 25 709
  • 內(nèi)容抽屜菜單ListViewWebViewSwitchButton按鈕點(diǎn)贊按鈕進(jìn)度條TabLayout圖標(biāo)下拉刷新...
    皇小弟閱讀 47,163評(píng)論 22 665
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,397評(píng)論 4 61
  • 1,從本篇文章/音頻/視頻中我學(xué)到的最重要的概念 把學(xué)到的英語(yǔ)單詞都應(yīng)用到實(shí)際生活中。學(xué)習(xí)一門(mén)語(yǔ)言要的是多用才...
    賈文慧應(yīng)數(shù)二班閱讀 303評(píng)論 1 0
  • 在cocos2d-x 3.15中可以通過(guò)序列幀來(lái)實(shí)現(xiàn)動(dòng)畫(huà)效果,具體實(shí)現(xiàn)方法就不說(shuō)了。不過(guò),在這個(gè)過(guò)程中有個(gè)地方需要...
    Kerwin_lang閱讀 677評(píng)論 0 0

友情鏈接更多精彩內(nèi)容