SlidingTabColors
之前阅读过Android Samples中UI的部分,过后发现很多知识点都忘了。今天开始把看过的东西都整理记录一下,从小事做起吧。与君共勉。
源代码在/android-23/ui/SlidingTabsColors下。
- 使用ViewPager滑动界面
- Canvas绘图
- Fragment的使用
- 动态添加View
- ScrollView的使用
目录
数据的交互
MainActivity
源码中主界面包含两个Fragment,上面的主要功能是打Log,下面的Fragment是核心功能的实现。
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
SlidingTabsColorsFragment fragment = new SlidingTabsColorsFragment();
transaction.replace(R.id.sample_content_fragment, fragment);
transaction.commit();
ContentFragment
此类为ViewPager中Adapter的Item。
public class ContentFragment extends Fragment {
//使用Bundle传递数据。
public static ContentFragment newInstance(CharSequence title, int indicatorColor,
int dividerColor) {
Bundle bundle = new Bundle();
bundle.putCharSequence(KEY_TITLE, title);
bundle.putInt(KEY_INDICATOR_COLOR, indicatorColor);
bundle.putInt(KEY_DIVIDER_COLOR, dividerColor);
ContentFragment fragment = new ContentFragment();
fragment.setArguments(bundle);
return fragment;
}
//该Fragment中包含三个TextView,分别显示文字,指示器颜色,分割线颜色。
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Bundle args = getArguments();
if (args != null) {
TextView title = (TextView) view.findViewById(R.id.item_title);
title.setText("Title: " + args.getCharSequence(KEY_TITLE));
int indicatorColor = args.getInt(KEY_INDICATOR_COLOR);
TextView indicatorColorView = (TextView) view.findViewById(R.id.item_indicator_color);
indicatorColorView.setText("Indicator: #" + Integer.toHexString(indicatorColor));
indicatorColorView.setTextColor(indicatorColor);
int dividerColor = args.getInt(KEY_DIVIDER_COLOR);
TextView dividerColorView = (TextView) view.findViewById(R.id.item_divider_color);
dividerColorView.setText("Divider: #" + Integer.toHexString(dividerColor));
dividerColorView.setTextColor(dividerColor);
}
}
}
SlidingTabsColorsFragment
在SlidingTabsColorsFragment中添加ViewPager,而ViewPager的中的内容是ContentFragment,由下面这个类的createFragment()方法提供提供。
static class SamplePagerItem {
private final CharSequence mTitle;
private final int mIndicatorColor;
private final int mDividerColor;
SamplePagerItem(CharSequence title, int indicatorColor, int dividerColor) {
mTitle = title;
mIndicatorColor = indicatorColor;
mDividerColor = dividerColor;
}
/**
* @return A new {@link Fragment} to be displayed by a {@link ViewPager}
* 获取ContentFragment()的方法
*/
Fragment createFragment() {
return ContentFragment.newInstance(mTitle, mIndicatorColor, mDividerColor);
}
/**
* @return the title which represents this tab. In this sample this is used directly by
* {@link android.support.v4.view.PagerAdapter#getPageTitle(int)}
* ViewPager中的文本内容
*/
CharSequence getTitle() {
return mTitle;
}
/**
* @return the color to be used for indicator on the {@link SlidingTabLayout}
* ScrollView中下面指示器的颜色
*/
int getIndicatorColor() {
return mIndicatorColor;
}
/**
* @return the color to be used for right divider on the {@link SlidingTabLayout}
* ScrollView中分割线的颜色
*/
int getDividerColor() {
return mDividerColor;
}
}
//使用ArrayList创建几个ContentFragment
private List<SamplePagerItem> mTabs = new ArrayList<SamplePagerItem>();
//选择FragmentPagerAdapter作为ViewPager的Adapter。
class SampleFragmentPagerAdapter extends FragmentPagerAdapter {
SampleFragmentPagerAdapter(FragmentManager fm) {
super(fm);
}
//获取item,类型为ContentFragment
@Override
public Fragment getItem(int i) {
return mTabs.get(i).createFragment();
}
@Override
public int getCount() {
return mTabs.size();
}
//显示内容文字
@Override
public CharSequence getPageTitle(int position) {
return mTabs.get(position).getTitle();
}
}
mViewPager = (ViewPager) view.findViewById(R.id.viewpager);
mViewPager.setAdapter(new SampleFragmentPagerAdapter(getChildFragmentManager()));
自定义的View
滑动条需要手动实现,原理是在HorizontalScrollView中添加一个LinearLayout,再在LinearLayout中添加几个TextView,当然也可以是其他的ui控件。
SlidingTabLayout
SlidingTabLayout继承自HorizontalScrollView
public SlidingTabLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// Disable the Scroll Bar
setHorizontalScrollBarEnabled(false);
// Make sure that the Tab Strips fills this View
setFillViewport(true);
//首先添加一个LinearLayout
mTabStrip = new SlidingTabStrip(context);
addView(mTabStrip, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
}
前面ViewPager的Item有几个,那么在LinearLayout中也添加几个TextView。
private void populateTabStrip() {
final PagerAdapter adapter = mViewPager.getAdapter();
final View.OnClickListener tabClickListener = new TabClickListener();
for (int i = 0; i < adapter.getCount(); i++) {
View tabView = null;
TextView tabTitleView = null;
if (tabView == null) {
tabView = createDefaultTabView(getContext());
}
tabTitleView.setText(adapter.getPageTitle(i));
//给TextView添加点击事件,点击textview,ViewPager调转到对应id的Item
tabView.setOnClickListener(tabClickListener);
mTabStrip.addView(tabView);
}
}
//刚初始化时,ScrollView显示在第一个item的位置
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (mViewPager != null) {
scrollToTab(mViewPager.getCurrentItem(), 0);
}
}
/*需要注意scrollToTab()方法中的scrollTo(x,y)方法,该方法在ScrollView中,其中的两个参数指的是scrollView的偏移量。
scrollView第一次在界面上显示时,它的左边应该是贴着手机的左边的(不一定在手机的左上角),View的左上角的坐标为(0,0),假如我们调用
scrollTo(x,y)方法,那么整个ScrollView会移动(-x,-y)的距离,即
scrollView上的(x,y)点会移动到刚刚(0,0)的位置。--我的理解是这样的。
*/
private void scrollToTab(int tabIndex, int positionOffset) {
final int tabStripChildCount = mTabStrip.getChildCount();
if (tabStripChildCount == 0 || tabIndex < 0 || tabIndex >= tabStripChildCount) {
return;
}
View selectedChild = mTabStrip.getChildAt(tabIndex);
if (selectedChild != null) {
/*targetScrollX就是ScrollView将要将要水平移动的位置,此处设置的是比positionOffset要小一些。比如Viewpager移动到
了下一个,则positionOffset为一个TextView的宽度,但是我们让他移动positionOffset-mTitleOffset个宽度,那么就可以显示上一个
TextView的一部分了。
*/
int targetScrollX = selectedChild.getLeft() + positionOffset;
if (tabIndex > 0 || positionOffset > 0) {
// If we're not at the first child and are mid-scroll, make sure we obey the offset
targetScrollX -= mTitleOffset;
}
scrollTo(targetScrollX, 0);
}
}
//添加ViewPagerListener,为了实现滑动ViewPager,指示器也跟着运动。
private class InternalViewPagerListener implements ViewPager.OnPageChangeListener {
private int mScrollState;
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
int tabStripChildCount = mTabStrip.getChildCount();
//position不能等于tabStripChildCount,后面要获取
//nextView,若等于,nextView为空。
if ((tabStripChildCount == 0) || (position < 0) || (position >= tabStripChildCount)) {
return;
}
//设置extraOffset,只是为了界面好看一些。
mTabStrip.onViewPagerPageChanged(position, positionOffset);
View selectedTitle = mTabStrip.getChildAt(position);
int extraOffset = (selectedTitle != null)
? (int) (positionOffset * selectedTitle.getWidth())
: 0;
scrollToTab(position, extraOffset);
}
//点击了TextView,ScrollView也要跟着改变,如果position>0,ScrollView会跟着滚动。
@Override
public void onPageSelected(int position) {
if (mScrollState == ViewPager.SCROLL_STATE_IDLE) {
mTabStrip.onViewPagerPageChanged(position, 0f);
scrollToTab(position, 0);
}
}
}
//LinearLayout中textView的点击事件
private class TabClickListener implements View.OnClickListener {
@Override
public void onClick(View v) {
for (int i = 0; i < mTabStrip.getChildCount(); i++) {
//获取点击的TextView的index,选择Viewpager相应的
//item
if (v == mTabStrip.getChildAt(i)) {
mViewPager.setCurrentItem(i);
return;
}
}
}
}
SlidingTabStrip
SlidingTabStrip继承自LinearLayout,即上面提到的LinearLayout,他是SlidingTabLayout的子View。我们在其中添加它的子View,也就是TextView,还需要在几个TextView的中间画分割线,最重要的是该View下方的指示器,他会跟随ViewPager的翻页移动到相应的位置。
//滑动Viewpager时调用了此方法,传入当前ViewPager的item
//position和滑动百分比,调用invalidate()来刷新view。
void onViewPagerPageChanged(int position, float positionOffset) {
mSelectedPosition = position;
mSelectionOffset = positionOffset;
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
final int height = getHeight();
final int childCount = getChildCount();
// 设置分割线为半个TextView高度(随便一个值,好看就行)
final int dividerHeightPx = (int) (Math.min(Math.max(0f, mDividerHeight), 1f) * height);
// Thick colored underline below the current selection
if (childCount > 0) {
//获取子View和它的宽高
View selectedTitle = getChildAt(mSelectedPosition);
int left = selectedTitle.getLeft();
int right = selectedTitle.getRight();
//通过一个interface获取当前positon对应SamplePagerItem的颜色。
int color = tabColorizer.getIndicatorColor(mSelectedPosition);
if (mSelectionOffset > 0f && mSelectedPosition < (getChildCount() - 1)) {
int nextColor = tabColorizer.getIndicatorColor(mSelectedPosition + 1);
if (color != nextColor) {
color = blendColors(nextColor, color, mSelectionOffset);
}
// Draw the selection partway between the tabs
View nextTitle = getChildAt(mSelectedPosition + 1);
//left = 当前View的左侧+(nextView左侧-当前View的左侧)×offset,右侧同理。
left = (int) (mSelectionOffset * nextTitle.getLeft() +
(1.0f - mSelectionOffset) * left);
right = (int) (mSelectionOffset * nextTitle.getRight() +
(1.0f - mSelectionOffset) * right);
}
mSelectedIndicatorPaint.setColor(color);
canvas.drawRect(left, height - mSelectedIndicatorThickness, right,
height, mSelectedIndicatorPaint);
}
// Thin underline along the entire bottom edge
//在此View下方画一条线
canvas.drawRect(0, height - mBottomBorderThickness, getWidth(), height, mBottomBorderPaint);
// Vertical separators between the titles
for (int i = 0; i < childCount - 1; i++) {
View child = getChildAt(i);
mDividerPaint.setColor(tabColorizer.getDividerColor(i));
//共有childCount个TextView,中间有childCount-1个间隔,在其中画分割线。
canvas.drawLine(child.getRight(), separatorTop, child.getRight(),
separatorTop + dividerHeightPx, mDividerPaint);
}
}
要实现滑动过程中指示器颜色渐变,只需要在onDraw()中更新painter的Color就可以。
颜色渐变的方法如下:
//获取两个颜色进行中和,实现颜色过度
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);
}
实现效果
源码可百度android samples到ui目录下找。