据美国财经网站IPOScoop报道,搜狗将于美国东部时间11月6日在纽交所挂牌交易。该报道称,搜狗将以每股11美元至13美元的价格发行4500万股美国存托股票(ADS),拟融资约5.4亿美元。10月27日,搜狗对招股书进行了更新,将IPO发行价区间定为每股美国存托股票11美元至13美元,最高融资6.6275亿美元。
本篇来自 游资程序员 的投稿,分享了炫丽横向滚动选择星期控件,希望能够给大家带来帮助。
游资程序员 的博客地址:
http://blog.csdn.net/hzmming2008
最近项目需要一个横向选择的星期的控件,需求如下:
当用户点击某个星期时能自动的选中这个星期的时期,并且选中后能放大以区分未选中的。
点击后能够自动滚动到屏幕中间的位置。
当用户滑动时不改变选中状态,但是滑动时时期跟随手指移动。
效果图如下:
从上图的效果看,很明显现有的控件无法满足,因此需要自定义 view。
从上图的效果看都是文字并且要可以点击和滑动,所以自然的想到可以用 textview 加载到 viewgroup 中去,然后重写 viewgroup的onTouchEvent 方法来实现滑动。
其次可以重写 viewgroup 的 onLayout 的方法来实现横向排列 textView。另外 textView 还需要有点击事件,因此必然需要重写 viewgroup 的 onInterceptTouchEvent 方法,来解决滑动冲突。下面是一步一步的去实现它。
控件初始化
因为控件需要滑动,我们在初始化时一并初始化定义的 mTouchSlop,它是用来来获取手机滑动的最小值,只有大于他时才认为是滑动。另外初始化一个 Scroller,用来实现如上面效果图的平滑滚动。
OnWeekClickListener mListener;//用户点击时的回调接口
private Scroller mScroller;//用于完成滚动操作的实例
private int mTouchSlop; //判定为拖动的最小移动像素数
List<NodeInterFace> mDatas = new ArrayList<>();//显示的数据
int selectIndex=2;//默认选中第三个
int itemWidth; private int view_margin_left_or_right;//view两边的的margin
private int leftBorder;//左边界
private int rightBorder;//右边界
public Week(Context context) { this(context,null);
}
public Week(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public Week(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 第一步,创建Scroller的实例
mScroller = new Scroller(context);
ViewConfiguration configuration = ViewConfiguration.get(context);
// 获取TouchSlop值
mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
}
重写onMeasure
由于本控件宽度是填充父窗体,高度是包裹内容,所以我们只需重新设置一下高度即可,而高度只需要随便获取一个 textview 的高度即可,另外为了让他有 padding 的效果,我们设置 viewgoup 的高度为 textView 的1.5倍,代码如下:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { measureChildren(widthMeasureSpec,heightMeasureSpec);
int defaultHeight=0, childHeight=0;
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if(heightSpecMode == MeasureSpec.AT_MOST){//高设置为wrap_content
for (int i=0;i<getChildCount();i++){ //由于只有一行 所以随便取一个的高度即可
childHeight= (int) (getChildAt(0).getMeasuredHeight()*1.5);
}
setMeasuredDimension(widthSpecSize,childHeight); //无论用户将宽设置为何种模式 都与match_parent相同
} else{
//宽高都设置为match_parenth或具体的dp值 setMeasuredDimension(widthSpecSize, heightSpecSize);
}
}
重写onLayout
从效果图可以看到选中的 textview 是居中且放大的,另外两边都是两个 textview,所以一屏就是5个 view,每个 textView 的宽度就是 itemWidth=getWidth()/5,另外他的view_margin_left_or_right 就相当于是 textview 的左右的 margin, view_margin_left_or_right=(itemWidth-getChildAt(0).getMeasuredWidth())/2。为了实现居中效果,现将选中的 textView 居中放置,再依次布局左右两边的textView的位置。另外需要在布置完成后,初始化左右边界即 leftBorder,rightBorder。这是用来在 onTouchEvent 方法中检查是否滑出边界。代码如下:
@Override
protected void onLayout(boolean b, int i, int i1, int i2, int i3) { itemWidth=getWidth()/5; //每行显示五个
int right,bottom,left;
bottom=getHeight()-(getHeight()-getChildAt(0).getMeasuredHeight())/2;
//itemWidth的宽度一定是大于 实际的每个子view的宽度的
view_margin_left_or_right=(itemWidth-getChildAt(0).getMeasuredWidth())/2;//相当于左右边距
int left_X=getWidth()-itemWidth*3;//中间一个的左边界坐标
for (int j=selectIndex-1;j >= 0;j--){ //中间那个view左边的那些view
View view=getChildAt(j);
view.setScaleX(1.0f);
view.setScaleY(1.0f);
right=left_X-view_margin_left_or_right-itemWidth*(selectIndex-1-j);
view.layout(right-getChildAt(j).getMeasuredWidth(),bottom-getChildAt(j).getMeasuredHeight(),right,bottom);
}
int right_X=itemWidth*3;;//中间一个的右边界坐标
for (int m=selectIndex+1;m<getChildCount();m++){ //中间那个view右边的那些view
View view=getChildAt(m);
view.setScaleX(1.0f);
view.setScaleY(1.0f);
left=right_X+view_margin_left_or_right+itemWidth*(m-(selectIndex+1));
view.layout(left,bottom-getChildAt(m).getMeasuredHeight(),left+getChildAt(m).getMeasuredWidth(),bottom);
}
//中间一个view
left=itemWidth*2+view_margin_left_or_right;
getChildAt(selectIndex).layout(left,bottom-getChildAt(selectIndex).getMeasuredHeight(),left+getChildAt(selectIndex).getMeasuredWidth(),bottom);
getChildAt(selectIndex).setScaleX(1.2f);
getChildAt(selectIndex).setScaleY(1.2f);
// 初始化左右边界值
leftBorder = getChildAt(0).getLeft();
rightBorder = getChildAt(getChildCount() - 1).getRight();
}
重写onInterceptTouchEvent
在这通过 mTouchSlop 来判断用户是滑动还是点击,只有大于 mTouchSlop 才认为是滑动,当是滑动时返回 true 对事件拦截掉,不让其传到textView以便调用 viewgroup 的onTouchEvent 来进行滑动。代码如下:
/**
* 手机按下时的屏幕坐标
*/
private float mXDown;
/**
* 手机当时所处的屏幕坐标
*/
private float mXMove;
/**
* 上次触发ACTION_MOVE事件时的屏幕坐标
*/
private float mXLastMove; @Override
public boolean onInterceptTouchEvent(MotionEvent ev) { switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mXDown = ev.getRawX();
mXLastMove = mXDown;
break;
case MotionEvent.ACTION_MOVE:
mXMove = ev.getRawX();
float dx=Math.abs(mXMove-mXDown);
if (dx>mTouchSlop){
return true;
}
break;
}
return super.onInterceptTouchEvent(ev);
}
重写onTouchEvent
在 ACTION_MOVE 事件中计算用户手指滑动的距离,并通过 scrollBy 来滑动响应距离。注意判断是否滑出了屏幕的边界,滑出边界就调用scrollTo回到边界,否则调用scrollBy(scrolledX, 0) 滑动相应的距离,里面得注释很详细了,就不多说了。
@Override
public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
mXMove = event.getRawX();
//计算本次view的移动距离 int scrolledX = (int) (mXLastMove - mXMove);
if (getScrollX() + scrolledX < leftBorder) {
//以前滑动的距离加上本次滑动的距离比左边第一个view的lfet小 既为滑出左边界了 滑出左边界是向右滑动所以getScrollX()为负值 scrollTo(leftBorder-view_margin_left_or_right, 0);
return true;
} else if (getScrollX() + scrolledX > rightBorder-getWidth()) {
//以前滑动的距离加上本次滑动的距离比右边最后一个view的right减去viewGroup的宽度大 既为滑出右边界了 滑出右边界是向左滑动所以getScrollX()为正值 scrollTo(rightBorder+view_margin_left_or_right - getWidth(), 0);
return true;
}
scrollBy(scrolledX, 0);
mXLastMove = mXMove;
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
数据的加载与显示
我们在这里通过setData方法把链表中的数据显示到textView上,并将链表的位置信息放在textView的tag中,用来判断用户点击的位置,最后通过viewgroup的addview方法将textView加载到viewgroup中来。
public void setData(List<NodeInterFace> mList, OnWeekClickListener listener){
mListener=listener;
mDatas=mList;
if (mDatas!=null){
for (int i=0;i<mDatas.size();i++){
TextView tv=(TextView) LayoutInflater.from(getContext()).inflate(R.layout.item_view,this,false);
tv.setText(mDatas.get(i).getDate());
tv.setTag(i);
tv.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
int pos=(int)view.getTag();
startAnim(pos, selectIndex);
selectIndex=pos;
Log.e("pos:",pos+"");
mListener.onClick( mDatas.get(pos).getDate());
}
});
if (i>1 && mDatas.get(i).isSelected() && i< mDatas.size()-2 ){
selectIndex=i;
}
addView(tv);
}
}
}
下面是 R.layout.item_view 的布局:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#fff"
android:textSize="10sp"
android:gravity="center"
android:background="@drawable/tv_bg" />
下面是 textview 的圆形背景:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval"
android:useLevel="false">
<!-- 实心 -->
<solid android:color="#383" />
<!-- 圆角 -->
<corners android:radius="360dp" />
<!-- 边距 -->
<padding android:bottom="1dp"
android:left="1dp"
android:right="1dp"
android:top="1dp" />
<!-- 大小 -->
<size android:width="50dp" android:height="50dp" />
</shape>
用户点击后开始滚动的动画
上面你看到了用户点击后调用了startAnim方法来实现滚动和点击后的textView的放大与缩小,当用户点击后我们首先通过ObjectAnimator对当前选中的textView进行放大,并对之前选中的textView进行缩小,再计算当前点击的textView需要滚动多少距离才能滚动的屏幕的中间,最后通过mScroller来进行平滑的滚动,代码如下:
private void startAnim(int current, int last){
if (current==last) return;
ObjectAnimator anim1_current = ObjectAnimator.ofFloat(getChildAt(current), "scaleX", 1.0f, 1.2f);
ObjectAnimator anim2_current = ObjectAnimator.ofFloat(getChildAt(current), "scaleY", 1.0f, 1.2f);
ObjectAnimator anim1_last = ObjectAnimator.ofFloat(getChildAt(last), "scaleX", 1.2f, 1.0f);
ObjectAnimator anim2_last = ObjectAnimator.ofFloat(getChildAt(last), "scaleY", 1.2f, 1.0f);
AnimatorSet set=new AnimatorSet();
set.setDuration(500);
set.playTogether(anim1_current,anim2_current,anim1_last,anim2_last);
set.start();
int dx= getDeletaX( current, last);
if (getScrollX() + dx < leftBorder) { //以前滑动的距离加上本次滑动的距离比左边第一个view的lfet小 既为滑出左边界了 滑出左边界是向右滑动所以getScrollX()为负值
scrollTo(leftBorder-view_margin_left_or_right, 0);
return ;
} else if (getScrollX() + dx > rightBorder-getWidth()) {//以前滑动的距离加上本次滑动的距离比右边最后一个view的right减去viewGroup的宽度大 既为滑出右边界了 滑出右边界是向左滑动所以getScrollX()为正值
scrollTo(rightBorder+view_margin_left_or_right - getWidth(), 0);
return ;
}
// scrollBy(dx,0);
mScroller.startScroll(getScrollX(), 0, dx, 0,500);
invalidate();
}
平滑滚动需要重写 computeScroll:
public void computeScroll() {
// 第三步,重写computeScroll()方法,并在其内部完成平滑滚动的逻辑
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
invalidate();
}
}
到此所有的工作就完成了,喜欢请点赞,谢谢。
源码地址如下:
http://download.csdn.net/download/hzmming2008/10025587
欢迎长按下图 -> 识别图中二维码
或者 扫一扫 关注我的公众号