在淘宝电影上面有这样一个功能,日期可以滑动,并且选中的是在正中间,效果如下:

20150611213914791.gif

看完了,那么问题来了。这个功能怎么实现呢?

我们先来分析一下: 
把功能拆分一下来看,如果不能滚动,是不是很好实现?其实就是一个 tab 栏,我在前面的 blog 中Android 快速实现 ViewPager 滑动页卡切换(可用作整个 app上导航) 中就实现了此功能,然后在此功能的基础上加上滚动功能即可,具体的实现原理是通过水平滚动控件 HorizontalScrollView把 tab 栏包含起来,然后通过  tab 的选中item 来控制HorizontalScrollView的滚动。

代码实现:

1、实现 自定义 tab,这里就不细讲了,跟前面那篇 blog 几乎一样,直接贴代码了,不清楚的请看前面的 blog

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package toolbar.scrollstripview;
 
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
 
/**
 * Created by moon.zhong on 2015/5/25.
 */
public class SlidingTabView extends LinearLayout {
 
    private static final float DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS = 0.5f;
    private static final byte DEFAULT_BOTTOM_BORDER_COLOR_ALPHA = 0x26;
    private static final int SELECTED_INDICATOR_THICKNESS_DIPS = 3;
    private static final int DEFAULT_SELECTED_INDICATOR_COLOR = 0xFF33B5E5;
 
    private static final int DEFAULT_DIVIDER_THICKNESS_DIPS = 1;
    private static final byte DEFAULT_DIVIDER_COLOR_ALPHA = 0x20;
    private static final float DEFAULT_DIVIDER_HEIGHT = 0.5f;
 
    private final float mBottomBorderThickness;
    private final Paint mBottomBorderPaint;
 
    private final int mSelectedIndicatorThickness;
    private final Paint mSelectedIndicatorPaint;
 
    private final Paint mDividerPaint;
    private final float mDividerHeight;
 
    private int mSelectedPosition;
    private float mSelectionOffset;
 
    public SlidingTabView(Context context) {
        this(context, null);
    }
 
    public SlidingTabView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setWillNotDraw(false);
        final float density = getResources().getDisplayMetrics().density;
 
        TypedValue outValue = new TypedValue();
        getContext().getTheme().resolveAttribute(android.R.attr.colorForeground, outValue, true);
        final int themeForegroundColor =  outValue.data;
 
        mBottomBorderThickness = (int) (DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS * density);
        mBottomBorderPaint = new Paint();
        mBottomBorderPaint.setColor(getResources().getColor(R.color.color_line));
 
        mSelectedIndicatorThickness = (int) (SELECTED_INDICATOR_THICKNESS_DIPS * density);
        mSelectedIndicatorPaint = new Paint();
        mSelectedIndicatorPaint.setColor(0xffff1322);
 
        mDividerHeight = DEFAULT_DIVIDER_HEIGHT;
        mDividerPaint = new Paint();
        mDividerPaint.setStrokeWidth((int) (DEFAULT_DIVIDER_THICKNESS_DIPS * density));
        mDividerPaint.setColor(getResources().getColor(R.color.color_line));
    }
 
    void viewPagerChange(int position, float offset){
        this.mSelectedPosition = position ;
        this.mSelectionOffset = offset ;
        if (offset == 0){
            for (int i = 0; i < getChildCount(); i++) {
                TextView child = (TextView) getChildAt(i);
                child.setTextColor(0xff666666);
            }
            TextView selectedTitle = (TextView) getChildAt(mSelectedPosition);
            selectedTitle.setTextColor(0xffff1322);
        }
        invalidate();
    }
 
    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        final int height = getHeight();
        final int childCount = getChildCount();
        final int dividerHeightPx = (int) (Math.min(Math.max(0f, mDividerHeight), 1f) * height);
 
        if (childCount > 0) {
            TextView selectedTitle = (TextView) getChildAt(mSelectedPosition);
            int left = selectedTitle.getLeft();
            int right = selectedTitle.getRight();
            selectedTitle.setTextColor(blendColors(0xff666666,0xffff1322,mSelectionOffset));
 
            if (mSelectionOffset > 0f && mSelectedPosition < (getChildCount() - 1)) {
 
                TextView nextTitle = (TextView) getChildAt(mSelectedPosition + 1);
                left = (int) (mSelectionOffset * nextTitle.getLeft() +
                        (1.0f - mSelectionOffset) * left);
                right = (int) (mSelectionOffset * nextTitle.getRight() +
                        (1.0f - mSelectionOffset) * right);
                nextTitle.setTextColor(blendColors(0xffff1322,0xff666666,mSelectionOffset));
            }
 
            canvas.drawRect(left, height - mSelectedIndicatorThickness, right,
                    height, mSelectedIndicatorPaint);
        }
 
        canvas.drawRect(0, height - mBottomBorderThickness, getWidth(), height, mBottomBorderPaint);
 
        int separatorTop = (height - dividerHeightPx) / 2;
        for (int i = 0; i < childCount - 1; i++) {
            View child = getChildAt(i);
 
            canvas.drawLine(child.getRight(), separatorTop, child.getRight(),
                    separatorTop + dividerHeightPx, mDividerPaint);
        }
    }
    private static int blendColors(int color1, int color2, float ratio) {
        final float inverseRation = 1f - ratio;
        float r = (Color.red(color1) * ratio) + (Color.red(color2) * inverseRation);
        float g = (Color.green(color1) * ratio) + (Color.green(color2) * inverseRation);
        float b = (Color.blue(color1) * ratio) + (Color.blue(color2) * inverseRation);
        return Color.rgb((int) r, (int) g, (int) b);
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    }
 
}

2、自定义HorizontalScrollView 
这个自定义类的功能,主要是填充 tab 的数据,通过选中的 item 来滚动HorizontalScrollView 
填充数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
    private void populateTabStrip() {
        final PagerAdapter adapter = mViewPager.getAdapter();
        final OnClickListener tabClickListener = new TabClickListener();
/**/
        /*通过 viewPager 的 item 来确定tab 的个数*/
 
        for (int i = 0; i < adapter.getCount(); i++) {
            View tabView = null;
            TextView tabTitleView = null;
 
            if (mTabViewLayoutId != 0) {
                // If there is a custom tab view layout id set, try and inflate it
                tabView = LayoutInflater.from(getContext()).inflate(mTabViewLayoutId, mTabStrip,
                        false);
                tabTitleView = (TextView) tabView.findViewById(mTabViewTextViewId);
            }
 
            if (tabView == null) {
                /*创建textView*/
                tabView = createDefaultTabView(getContext());
            }
 
            if (tabTitleView == null && TextView.class.isInstance(tabView)) {
                tabTitleView = (TextView) tabView;
            }
 
            tabTitleView.setText(adapter.getPageTitle(i));
            tabView.setOnClickListener(tabClickListener);
            tabView.setBackgroundResource(R.drawable.item_selector_bg);
            /*把 textView 放入到自定义的 tab 栏中*/
            mTabStrip.addView(tabView);
        }
    }

滚动 ScrollView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
    /**
     * 这个方法是关键
     * 滚动 scrollview
     * @param tabIndex
     * @param positionOffset
     */
    private void scrollToTab(int tabIndex, int positionOffset) {
        final int tabStripChildCount = mTabStrip.getChildCount();
        if (tabStripChildCount == 0 || tabIndex < 0 || tabIndex >= tabStripChildCount) {
            return;
        }
 
        /*获取当前选中的 item*/
        View selectedChild = mTabStrip.getChildAt(tabIndex);
        if (selectedChild != null) {
            /*获取当前 item 的偏移量*/
            int targetScrollX = selectedChild.getLeft() + positionOffset;
            /*item 的宽度*/
            int width = selectedChild.getWidth();
            /*item 距离正中间的偏移量*/
            mTitleOffset = (int) ((mWidth-width)/2.0f);
 
            if (tabIndex > 0 || positionOffset > 0) {
                /*计算出正在的偏移量*/
                targetScrollX -= mTitleOffset;
            }
            Log.v("zgy","==================mWidth======="+mWidth) ;
            /*这个时候偏移的量就是屏幕的正中间*/
            scrollTo(targetScrollX, 0);
        }
    }

具体的调用当然就是 在 ViewPager 的OnPageChangeListener中。 
完整类的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
package toolbar.scrollstripview;
 
import android.app.Activity;
import android.content.Context;
import android.os.Build;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.HorizontalScrollView;
import android.widget.TextView;
 
/**
 * Created by moon.zhong.
 */
public class SlidingTabLayout extends HorizontalScrollView {
    private static final int TITLE_OFFSET_DIPS = 24;
    private static final int TAB_VIEW_PADDING_DIPS_TB = 15;
    private static final int TAB_VIEW_PADDING_DIPS = 16;
    private static final int TAB_VIEW_TEXT_SIZE_SP = 14;
    private int mTitleOffset;
 
    private int mTabViewLayoutId;
    private int mTabViewTextViewId;
 
    private ViewPager mViewPager;
    private final SlidingTabView mTabStrip;
 
    private int mWidth ;
    public SlidingTabLayout(Context context) {
        this(context, null);
    }
 
    public SlidingTabLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
 
    public SlidingTabLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
 
        setHorizontalScrollBarEnabled(false);
 
        setFillViewport(true);
 
        mTitleOffset = (int) (TITLE_OFFSET_DIPS * getResources().getDisplayMetrics().density);
 
        mTabStrip = new SlidingTabView(context);
        addView(mTabStrip, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        DisplayMetrics displayMetrics = new DisplayMetrics() ;
        ((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
        mWidth = (int) (displayMetrics.widthPixels) ;
    }
    public void setViewPager(ViewPager viewPager) {
        mTabStrip.removeAllViews();
 
        mViewPager = viewPager;
        if (viewPager != null) {
            viewPager.addOnPageChangeListener(new InternalViewPagerListener());
            populateTabStrip();
        }
    }
    private void populateTabStrip() {
        final PagerAdapter adapter = mViewPager.getAdapter();
        final OnClickListener tabClickListener = new TabClickListener();
/**/
        /*通过 viewPager 的 item 来确定tab 的个数*/
 
        for (int i = 0; i < adapter.getCount(); i++) {
            View tabView = null;
            TextView tabTitleView = null;
 
            if (mTabViewLayoutId != 0) {
                // If there is a custom tab view layout id set, try and inflate it
                tabView = LayoutInflater.from(getContext()).inflate(mTabViewLayoutId, mTabStrip,
                        false);
                tabTitleView = (TextView) tabView.findViewById(mTabViewTextViewId);
            }
 
            if (tabView == null) {
                /*创建textView*/
                tabView = createDefaultTabView(getContext());
            }
 
            if (tabTitleView == null && TextView.class.isInstance(tabView)) {
                tabTitleView = (TextView) tabView;
            }
 
            tabTitleView.setText(adapter.getPageTitle(i));
            tabView.setOnClickListener(tabClickListener);
            tabView.setBackgroundResource(R.drawable.item_selector_bg);
            /*把 textView 放入到自定义的 tab 栏中*/
            mTabStrip.addView(tabView);
        }
    }
    /*这里就是创建 textView,没什么可讲的*/
    protected TextView createDefaultTabView(Context context) {
        TextView textView = new TextView(context);
        textView.setGravity(Gravity.CENTER);
        textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, TAB_VIEW_TEXT_SIZE_SP);;
 
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
 
            TypedValue outValue = new TypedValue();
            getContext().getTheme().resolveAttribute(android.R.attr.selectableItemBackground,
                    outValue, true);
            textView.setBackgroundResource(outValue.resourceId);
        }
 
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            textView.setAllCaps(true);
        }
 
        int paddingTB = (int) (TAB_VIEW_PADDING_DIPS_TB * getResources().getDisplayMetrics().density);
        textView.setPadding(0, paddingTB, 0, paddingTB);
        textView.setTextColor(0xff666666);
        int width = (int) (100 * getResources().getDisplayMetrics().density);
        ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(width, ViewGroup.LayoutParams.WRAP_CONTENT) ;
        textView.setLayoutParams(params);
        return textView;
    }
 
    /**
     * 这个方法是关键
     * 滚动 scrollview
     * @param tabIndex
     * @param positionOffset
     */
    private void scrollToTab(int tabIndex, int positionOffset) {
        final int tabStripChildCount = mTabStrip.getChildCount();
        if (tabStripChildCount == 0 || tabIndex < 0 || tabIndex >= tabStripChildCount) {
            return;
        }
 
        /*获取当前选中的 item*/
        View selectedChild = mTabStrip.getChildAt(tabIndex);
        if (selectedChild != null) {
            /*获取当前 item 的偏移量*/
            int targetScrollX = selectedChild.getLeft() + positionOffset;
            /*item 的宽度*/
            int width = selectedChild.getWidth();
            /*item 距离正中间的偏移量*/
            mTitleOffset = (int) ((mWidth-width)/2.0f);
 
            if (tabIndex > 0 || positionOffset > 0) {
                /*计算出正在的偏移量*/
                targetScrollX -= mTitleOffset;
            }
            Log.v("zgy","==================mWidth======="+mWidth) ;
            /*这个时候偏移的量就是屏幕的正中间*/
            scrollTo(targetScrollX, 0);
        }
    }
 
 
    private class InternalViewPagerListener implements ViewPager.OnPageChangeListener {
        private int mScrollState;
 
        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            int tabStripChildCount = mTabStrip.getChildCount();
            if ((tabStripChildCount == 0) || (position < 0) || (position >= tabStripChildCount)) {
                return;
            }
 
            mTabStrip.viewPagerChange(position, positionOffset);
 
            View selectedTitle = mTabStrip.getChildAt(position);
            int extraOffset = (selectedTitle != null)
                    ? (int) (positionOffset * selectedTitle.getWidth())
                    : 0;
            scrollToTab(position, extraOffset);
 
 
        }
 
        @Override
        public void onPageScrollStateChanged(int state) {
            mScrollState = state;
 
        }
 
        @Override
        public void onPageSelected(int position) {
            if (mScrollState == ViewPager.SCROLL_STATE_IDLE) {
                mTabStrip.viewPagerChange(position, 0f);
                scrollToTab(position, 0);
            }
 
        }
 
    }
    private class TabClickListener implements OnClickListener {
        @Override
        public void onClick(View v) {
            for (int i = 0; i < mTabStrip.getChildCount(); i++) {
                if (v == mTabStrip.getChildAt(i)) {
                    mViewPager.setCurrentItem(i);
                    return;
                }
            }
        }
    }
}

代码引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
 
    <toolbar.scrollstripview.SlidingTabLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/id_sliding_view"/>
    <android.support.v4.view.ViewPager
        android:id="@+id/id_view_pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@id/id_sliding_view"/>
</RelativeLayout>

运行效果:

20150611221023785.gif

总结: 
1、对前面Android 快速实现 ViewPager 滑动页卡切换(可用作整个 app上导航) blog 的运用; 
2、scrollTo(targetScrollX, 0);方法的灵活运用,targetScrollX如果小于0,获取targetScrollX大于 view 的宽度的时候这个方法都不会起作用,看源码可知:

1
2
3
4
5
6
7
8
9
10
11
    public void scrollTo(int x, int y) {
        // we rely on the fact the View.scrollBy calls scrollTo.
        if (getChildCount() > 0) {
            View child = getChildAt(0);
            x = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth());
            y = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight());
            if (x != mScrollX || y != mScrollY) {
                super.scrollTo(x, y);
            }
        }
    }

对 x、y 做了相应的截取操作

源码下载

http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0615/3045.html