配色: 字号:
Android自定义View(RollWeekView-炫酷的星期日期选择控件)
2016-12-01 | 阅:  转:  |  分享 
  
Android自定义View(RollWeekView-炫酷的星期日期选择控件)



一次展示一个星期的5天,中间放大的为当前选中的;如果点击了其中一个日期,比如星期五,那么整体向左滑动,并将星期五慢慢放大,星期三慢慢缩小,星期六和星期天就展示出来了;如果继续点击星期六,那么照样是整体向左滑动一个单位,星期日右边就需要塞进一个星期一。就是一个无限循环的过程,中间选中的需要展示当天的日期。



1、分析



??最开始,我想到在LinearLayout中依次排开7个子控件(星期几),当点击之后,把所有的子控件通过动画移动相应的偏移量,但是,如果移动后就会出现空位的情况(周一的左边和周日的右边没有了),所以这种方式可是可以,但是要解决无限循环空位的情况。

??于是就想到能不能多添加几个子控件作为空位替补?这个方法肯定能实现,但是究竟需要多少个替补呢?这些替补应该放在什么位置呢?

??当前展示的5个子控件中,如果点击最左边或者最右边,这样的偏移量是最大的,需要便宜两个子控件单位,所以左边和右边最多个需要2个替补就行了,算起来也不多,一共才9个(当前展示5个,左边隐藏替补2个,右边隐藏替补2个)。在点击之前,先将替补位置设置好,当点击之后,所有子控件执行偏移动画(不可能出现空位,最多偏移2位),动画结束之后,再重新为隐藏的4个替补分配位置并绑定数据,绑定什么数据那就看当前中间显示的是星期几了。



分析图如下:



但是新的问题又来了,动画执行完毕之后,怎么判断哪些子控件是当前正在显示的呢(中间五个)?由于正在显示的子控件的位置是可以确定的,因为要重写onMeasure(),在onMeasure()方法中可以得到单个子控件的宽度(item_width=getMeasureWidth()/5),动画结束之后,遍历这些子控件,如果x==0的那肯定就是当前正在展示的第一个子控件,x==item_width则是第二个….,剩余的4个就不用判断了,直接将两个移动到左边,两个移动到右边,然后重新为这四个替补设置数据。



2、定义控件布局



组合控件的布局如下:






android:layout_width="match_parent"

android:layout_height="match_parent"

android:orientation="horizontal"

android:gravity="center_vertical">




android:id="@+id/ll_1"

android:layout_width="wrap_content"

android:layout_height="match_parent"

android:gravity="center">


android:layout_width="@dimen/week_item_sise"

android:layout_height="@dimen/week_item_sise"

android:background="@drawable/week_one_bg"

android:orientation="vertical"

android:gravity="center">


android:id="@+id/tv_1"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:gravity="center"

android:text="周"/>


android:id="@+id/tv_date1"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:gravity="center"

android:text="周"/>








android:id="@+id/ll_2"

android:layout_width="wrap_content"

android:layout_height="match_parent"

android:gravity="center">


android:layout_width="@dimen/week_item_sise"

android:layout_height="@dimen/week_item_sise"

android:background="@drawable/week_one_bg"

android:orientation="vertical"

android:gravity="center">


android:id="@+id/tv_2"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:gravity="center"

android:text="周"/>


android:id="@+id/tv_date2"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:gravity="center"

android:text="周"/>






android:id="@+id/ll_3"

android:layout_width="wrap_content"

android:layout_height="match_parent"

android:gravity="center">


android:layout_width="@dimen/week_item_sise"

android:layout_height="@dimen/week_item_sise"

android:background="@drawable/week_one_bg"

android:orientation="vertical"

android:gravity="center">


android:id="@+id/tv_3"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:gravity="center"

android:text="周"/>


android:id="@+id/tv_date3"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:gravity="center"

android:text="周"/>








android:id="@+id/ll_4"

android:layout_width="wrap_content"

android:layout_height="match_parent"

android:gravity="center">


android:layout_width="@dimen/week_item_sise"

android:layout_height="@dimen/week_item_sise"

android:background="@drawable/week_one_bg"

android:orientation="vertical"

android:gravity="center">


android:id="@+id/tv_4"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:gravity="center"

android:text="周"/>


android:id="@+id/tv_date4"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:gravity="center"

android:text="周"/>








android:id="@+id/ll_5"

android:layout_width="wrap_content"

android:layout_height="match_parent"

android:gravity="center">


android:layout_width="@dimen/week_item_sise"

android:layout_height="@dimen/week_item_sise"

android:background="@drawable/week_one_bg"

android:orientation="vertical"

android:gravity="center">


android:id="@+id/tv_5"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:gravity="center"

android:text="周"/>


android:id="@+id/tv_date5"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:gravity="center"

android:text="周"/>








android:id="@+id/ll_6"

android:layout_width="wrap_content"

android:layout_height="match_parent"

android:gravity="center">


android:layout_width="@dimen/week_item_sise"

android:layout_height="@dimen/week_item_sise"

android:background="@drawable/week_one_bg"

android:orientation="vertical"

android:gravity="center">


android:id="@+id/tv_6"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:gravity="center"

android:text="周"/>


android:id="@+id/tv_date6"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:gravity="center"

android:text="周"/>








android:id="@+id/ll_7"

android:layout_width="wrap_content"

android:layout_height="match_parent"

android:gravity="center">


android:layout_width="@dimen/week_item_sise"

android:layout_height="@dimen/week_item_sise"

android:background="@drawable/week_one_bg"

android:orientation="vertical"

android:gravity="center">


android:id="@+id/tv_7"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:gravity="center"

android:text="周"/>


android:id="@+id/tv_date7"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:gravity="center"

android:text="周"/>








android:id="@+id/ll_8"

android:layout_width="wrap_content"

android:layout_height="match_parent"

android:gravity="center">


android:layout_width="@dimen/week_item_sise"

android:layout_height="@dimen/week_item_sise"

android:background="@drawable/week_one_bg"

android:orientation="vertical"

android:gravity="center">


android:id="@+id/tv_8"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:gravity="center"

android:text="周"/>


android:id="@+id/tv_date8"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:gravity="center"

android:text="周"/>








android:id="@+id/ll_9"

android:layout_width="wrap_content"

android:layout_height="match_parent"

android:gravity="center">


android:layout_width="@dimen/week_item_sise"

android:layout_height="@dimen/week_item_sise"

android:background="@drawable/week_one_bg"

android:orientation="vertical"

android:gravity="center">


android:id="@+id/tv_9"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:gravity="center"

android:text="周"/>


android:id="@+id/tv_date9"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:gravity="center"

android:text="周"/>







布局写完后,接下来应该找一个容器接纳他们,这个容器就是我们定义的CustomWeekView,让其继承LinearLayout,然后重写构造方法,并初始化子控件;此处自定义属性就不再赘述,之前的所有自定义控件案例中都讲解过

publicclassCustomWeekViewextendsLinearLayout{

publicCustomWeekView(Contextcontext){

this(context,null);

}



publicCustomWeekView(Contextcontext,AttributeSetattrs){

this(context,attrs,0);

}



publicCustomWeekView(Contextcontext,AttributeSetattrs,intdefStyleAttr){

super(context,attrs,defStyleAttr);

init(context,attrs);

}

privatevoidinit(Contextcontext,AttributeSetattrs){

LayoutInflater.from(context).inflate(R.layout.custom_week_layout,this,true);

ll_1=(LinearLayout)findViewById(R.id.ll_1);

ll_2=(LinearLayout)findViewById(R.id.ll_2);

...

tv_1=(TextView)findViewById(R.id.tv_1);

tv_2=(TextView)findViewById(R.id.tv_2);

...

tv_date1=(TextView)findViewById(R.id.tv_date1);

tv_date2=(TextView)findViewById(R.id.tv_date2);

...

if(attrs!=null){

TypedArrayta=context.obtainStyledAttributes(attrs,R.styleable.LimitScroller);

limit=5;

background=ta.getColor(R.styleable.LimitScroller_android_background,Color.TRANSPARENT);

textSize=ta.getDimension(R.styleable.LimitScroller_android_textSize,15f);

finalfloatfontScale=context.getResources().getDisplayMetrics().scaledDensity;

textSize=textSize/fontScale+0.5f;

dateTextSize=ta.getInteger(R.styleable.LimitScroller_dateTextSize,12);

textColor=ta.getColor(R.styleable.LimitScroller_android_textColor,Color.BLACK);

dateTextColor=ta.getColor(R.styleable.LimitScroller_dateTextColor,Color.RED);

durationTime=ta.getInt(R.styleable.LimitScroller_durationTime,1000);

scaleSize=ta.getFloat(R.styleable.LimitScroller_scaleSize,0);

ta.recycle();//注意回收

}

}

}

4、重写onMeasure



??由于本控件宽度是填充父窗体,高度是包裹内容,这里不需要我们手动写代码测量,直接调用super.onMeasure()好了。。但是我们需要在测量完毕后,计算子控件的宽度并初次安置每个子控件的位置,这个宽度就是后面移动的单位,也作为位置判断的依据。由于onMeasure()方法会被调用多次(后面设置位置、显示隐藏等等操作都会导致onMeasure()触发),所以需要判断一下如果ITEM_WIDTH<=0才需要计算;然后还要为子控件设置初始日期:



@Override

protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){

super.onMeasure(widthMeasureSpec,heightMeasureSpec);

//Log.w(TAG,"测量完成,宽度高度="+getMeasuredWidth()+""+getMeasuredHeight());

if(ITEM_WIDTH<=0){

ITEM_WIDTH=getMeasuredWidth()/limit;

//Log.w(TAG,"每小项尺寸:"+ITEM_WIDTH+""+getMeasuredHeight());

measureInit();

}

if(ll_2.getX()>0&&!isFirstSeted){

//设置今天的日期在中间

animalFinish=false;

Calendarcal=Calendar.getInstance();

inttodayNum=cal.get(Calendar.DAY_OF_WEEK)-1;

Log.d(TAG,"今天是星期"+WEEK_STR[todayNum]);

setCenter(getEnumByNum(todayNum));

animalFinish=true;

isFirstSeted=true;

}

}



/根据屏幕的宽度和显示的个数,设置item的宽度/

privatevoidmeasureInit(){

LinearLayout.LayoutParamslp=(LinearLayout.LayoutParams)ll_1.getLayoutParams();

lp.width=ITEM_WIDTH;

ll_1.setLayoutParams(lp);

lp=(LinearLayout.LayoutParams)ll_2.getLayoutParams();

lp.width=ITEM_WIDTH;

ll_2.setLayoutParams(lp);

lp=(LinearLayout.LayoutParams)ll_3.getLayoutParams();

lp.width=ITEM_WIDTH;

ll_3.setLayoutParams(lp);

lp=(LinearLayout.LayoutParams)ll_4.getLayoutParams();

lp.width=ITEM_WIDTH;

ll_4.setLayoutParams(lp);

lp=(LinearLayout.LayoutParams)ll_5.getLayoutParams();

lp.width=ITEM_WIDTH;

ll_5.setLayoutParams(lp);

lp=(LinearLayout.LayoutParams)ll_6.getLayoutParams();

lp.width=ITEM_WIDTH;

ll_6.setLayoutParams(lp);

lp=(LinearLayout.LayoutParams)ll_7.getLayoutParams();

lp.width=ITEM_WIDTH;

ll_7.setLayoutParams(lp);

lp=(LinearLayout.LayoutParams)ll_8.getLayoutParams();

lp.width=ITEM_WIDTH;

ll_8.setLayoutParams(lp);

lp=(LinearLayout.LayoutParams)ll_9.getLayoutParams();

lp.width=ITEM_WIDTH;

ll_9.setLayoutParams(lp);

}

5、点击后执行动画



??上面的步骤完成之后,基本上已经完成的控件的初始设置,接下来就是点击子控件后执行动画了。这里一共需要执行两套动画,一套是缩放动画、一套是移动动画。添加点击事件的代码就不贴出来了,下面是执行动画的方法:



privatevoidstartAnimation(finalWEEKDAYcenterWitch,finalLinearLayoutllClickView){



if(centerWitch==centerNow)

return;

animalFinish=false;



LinearLayoutinnerLL=(LinearLayout)ll3.getChildAt(0);

TextViewtvDate=(TextView)innerLL.getChildAt(1);

tvDate.setVisibility(View.GONE);

innerLL=(LinearLayout)llClickView.getChildAt(0);

tvDate=(TextView)innerLL.getChildAt(1);

tvDate.setVisibility(View.VISIBLE);



//根据当前中间位置显示的和被点击的日期,获取需要偏移的增量

intoffset=getXOffset(centerWitch);

Log.d(TAG,"当前中间为"+centerNow+",点击的是"+centerWitch+"偏移量:"+offset);



//当前中间位置的需要缩放到原尺寸

//Log.v(TAG,"中间item缩放量scaleX="+ll3.getChildAt(0).getScaleX()+"scaleY="+ll3.getChildAt(0).getScaleY());

ObjectAnimatoranim100=ObjectAnimator.ofFloat(ll3.getChildAt(0),"scaleX",ll3.getChildAt(0).getScaleX(),1.0f);

ObjectAnimatoranim101=ObjectAnimator.ofFloat(ll3.getChildAt(0),"scaleY",ll3.getChildAt(0).getScaleY(),1.0f);

//被点击的需要放大

ObjectAnimatoranim102=ObjectAnimator.ofFloat(llClickView.getChildAt(0),"scaleX",1,scaleSize);

ObjectAnimatoranim103=ObjectAnimator.ofFloat(llClickView.getChildAt(0),"scaleY",1,scaleSize);



//透明度动画

ObjectAnimatoranim104=ObjectAnimator.ofFloat(llClickView.getChildAt(0),"scaleY",1,scaleSize);



//位移动画

ObjectAnimatoranim1=ObjectAnimator.ofFloat(ll_1,"X",ll_1.getX(),ll_1.getX()+offset);

ObjectAnimatoranim2=ObjectAnimator.ofFloat(ll_2,"X",ll_2.getX(),ll_2.getX()+offset);

ObjectAnimatoranim3=ObjectAnimator.ofFloat(ll_3,"X",ll_3.getX(),ll_3.getX()+offset);

ObjectAnimatoranim4=ObjectAnimator.ofFloat(ll_4,"X",ll_4.getX(),ll_4.getX()+offset);

ObjectAnimatoranim5=ObjectAnimator.ofFloat(ll_5,"X",ll_5.getX(),ll_5.getX()+offset);

ObjectAnimatoranim6=ObjectAnimator.ofFloat(ll_6,"X",ll_6.getX(),ll_6.getX()+offset);

ObjectAnimatoranim7=ObjectAnimator.ofFloat(ll_7,"X",ll_7.getX(),ll_7.getX()+offset);

ObjectAnimatoranim8=ObjectAnimator.ofFloat(ll_8,"X",ll_8.getX(),ll_8.getX()+offset);

ObjectAnimatoranim9=ObjectAnimator.ofFloat(ll_9,"X",ll_9.getX(),ll_9.getX()+offset);

AnimatorSetanimSet=newAnimatorSet();

animSet.setDuration(duwww.shanxiwang.netrationTime);

animSet.playTogether(anim100,anim101,anim102,anim103,anim1,anim2,anim3,anim4,anim5,anim6,anim7,anim8,anim9);

animSet.addListener(newAnimator.AnimatorListener(){

@Override

publicvoidonAnimationStart(Animatoranimation){

}

@Override

publicvoidonAnimationEnd(Animatoranimation){



Log.w(TAG,"动画结束后位置:"+ll_1.getX()+""+ll_2.getX()+""+ll_3.getX()+""

+ll_4.getX()+""+ll_5.getX()+""+ll_6.getX()

+""+ll_7.getX()+""+ll_8.getX()+""+ll_9.getX());



setCenter(centerWitch);



animalFinish=true;

}

@Override

publicvoidonAnimationCancel(Animatoranimation){

}

@Override

publicvoidonAnimationRepeat(Animatoranimation){

}

});

animSet.start();

}

7、重置预备控件



??动画执行完毕之后,要做两个操作。第一个就是将当前不在显示范围的4个子控件左右各放2个,第二步就是为预备控件绑定新的星期日期。下面的方法是根据各个子控件的坐标来判断是隐藏还是展示,然后为隐藏的控件重新安排位置;然后绑定数据:



/这些引用代表当前正在显示的5个条目和四个预备条目,

由于ll_x系列条目是不断移动的,所以此处需要根据ll_x的位置重新为llx赋值

其中ll1-ll5为当前正在显示的条目,ll6、ll7为右边隐藏的预备条目,ll8、ll9为左边的隐藏预备条目

/

privateLinearLayoutll1,ll2,ll3,ll4,ll5,ll6,ll7,ll8,ll9;

/1、找到正在展示的五个item,并将预备item复位/

privatevoidsetCenter(WEEKDAYweekDay){

//记录当前显示在中间的星期X

centerNow=weekDay;

//1、找到当前显示的5个条目的位置

Listlist=newArrayList<>(llList);

for(inti=0;i<5;i++){

for(intj=0;j
LinearLayoutll=list.get(j);

if(ll.getX()==ITEM_WIDTHi){

list.remove(ll);//找到之后就remove可以减少后面遍历的次数

//Log.d(TAG,"找到"+i+"了"+ll);

switch(i){

case0:

ll1=ll;

break;

case1:

ll2=ll;

break;

case2:

ll3=ll;

//当前中间位置item放大

ll3.getChildAt(0).setScaleX(scaleSize);

ll3.getChildAt(0).setScaleY(scaleSize);

break;

case3:

ll4=ll;

break;

case4:

ll5=ll;

break;

}

}

}

}

Log.i(TAG,"找完后还剩"+list.size()+"总:"+llList.size());

//2、剩余的四个作为预备,归位,左边隐藏两个,右边隐藏两个

for(inti=0;i
LinearLayoutll=list.get(i);

switch(i){

case0://左1

ll.setX(-ITEM_WIDTH2);

ll8=ll;

break;

case1://左2

ll.setX(-ITEM_WIDTH1);

ll9=ll;

break;

case2://右1

ll.setX(ITEM_WIDTH5);

ll6=ll;

break;

case3://右2

ll.setX(ITEM_WIDTH6);

ll7=ll;

break;

}

}

//绑定数据

reBoundDataByCenter(weekDay);

}



/2、重新绑定数据/

privatevoidreBoundDataByCenter(WEEKDAYweekDay){

if(weekDay==WEEKDAY.wk1){

/星期1在中间,依次为4、5、6、7、1、2、3、4、5/

setLLText(ll8,4,false);

setLLText(ll9,5,false);

setLLText(ll1,6,false);

setLLText(ll2,7,false);

setLLText(ll3,1,true);

setLLText(ll4,2,false);

setLLText(ll5,3,false);

setLLText(ll6,4,false);

setLLText(ll7,5,false);

}elseif(weekDay==WEEKDAY.wk2){

/星期2在中间,依次为5、6、7、1、2、3、4、5、6/

setLLText(ll8,5,false);

setLLText(ll9,6,false);

setLLText(ll1,7,false);

setLLText(ll2,1,false);

setLLText(ll3,2,true);

setLLText(ll4,3,false);

setLLText(ll5,4,false);

setLLText(ll6,5,false);

setLLText(ll7,6,false);

}elseif(weekDay==WEEKDAY.wk3){

/星期3在中间,依次为6、7、1、2、3、4、5、6、7/

setLLText(ll8,6,false);

setLLText(ll9,7,false);

setLLText(ll1,1,false);

setLLText(ll2,2,false);

setLLText(ll3,3,true);

setLLText(ll4,4,false);

setLLText(ll5,5,false);

setLLText(ll6,6,false);

setLLText(ll7,7,false);

}elseif(weekDay==WEEKDAY.wk4){

/星期4在中间,依次为7、1、2、3、4、5、6、7、1/

setLLText(ll8,7,false);

setLLText(ll9,1,false);

setLLText(ll1,2,false);

setLLText(ll2,3,false);

setLLText(ll3,4,true);

setLLText(ll4,5,false);

setLLText(ll5,6,false);

setLLText(ll6,7,false);

setLLText(ll7,1,false);

}elseif(weekDay==WEEKDAY.wk5){

/星期5在中间,依次为1、2、3、4、5、6、7、1、2/

setLLText(ll8,1,false);

setLLText(ll9,2,false);

setLLText(ll1,3,false);

setLLText(ll2,4,false);

setLLText(ll3,5,true);

setLLText(ll4,6,false);

setLLText(ll5,7,false);

setLLText(ll6,1,false);

setLLText(ll7,2,false);

}elseif(weekDay==WEEKDAY.wk6){

/星期6在中间,依次为2、3、4、5、6、7、1、2、3/

setLLText(ll8,2,false);

setLLText(ll9,3,false);

setLLText(ll1,4,false);

setLLText(ll2,5,false);

setLLText(ll3,6,true);

setLLText(ll4,7,false);

setLLText(ll5,1,false);

setLLText(ll6,2,false);

setLLText(ll7,3,false);

}elseif(weekDay==WEEKDAY.wk7){

/星期7在中间,依次为3、4、5、6、7、1、2、3、4/

setLLText(ll8,3,false);

setLLText(ll9,4,false);

setLLText(ll1,5,false);

setLLText(ll2,6,false);

setLLText(ll3,7,true);

setLLText(ll4,1,false);

setLLText(ll5,2,false);

setLLText(ll6,3,false);

setLLText(ll7,4,false);

}

}



privatevoidsetLLText(LinearLayoutll,intwitchDay,booleanshowDate){

ll.setTag(witchDay);//便于区分点击事件

LinearLayoutinnerLL=(LinearLayout)ll.getChildAt(0);

TextViewtv=(TextView)innerLL.getChildAt(0);

Stringtext="星期"+WEEK_STR[witchDay];

tv.setText(text);



TextViewtvDate=(TextView)innerLL.getChildAt(1);

text=DATE_STR.get(witchDay);

tvDate.setText(text);

if(showDate){

tvDate.setVisibility(View.VISIBLE);

}else{

tvDate.setVisibility(View.GONE);

}

}

?当执行完上面的方法后,控件的状态又回到了初始状态,下次点击又会重复上面的步骤。因为动画执行完后,只有被隐藏的子控件需要重新安排位置,所以,不会出现闪跳的现象。



最终效果图如下:





献花(0)
+1
(本文系网络学习天...首藏)