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ò)程:

左是從左往右平移一個(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è)筆記