Java tutorial
/* * Copyright (C) 2013 Peng fei Pan <sky@xiaopan.me> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package me.xiaopan.psts; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.drawable.Drawable; import android.os.Build; import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPager.OnPageChangeListener; import android.util.AttributeSet; import android.view.GestureDetector; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.widget.HorizontalScrollView; import android.widget.LinearLayout; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * ViewPager? */ public class PagerSlidingTabStrip extends HorizontalScrollView { private int currentPosition; //?? private int lastOffset; private int lastScrollX = 0; private float currentPositionOffset; //????? private boolean start; private boolean allowWidthFull; // ?Item private boolean disableViewPager; // ?ViewPager private Drawable slidingBlockDrawable; //? private ViewPager viewPager; //ViewPager private ViewGroup tabsLayout; // private ViewPager.OnPageChangeListener onPageChangeListener; //??? private OnClickTabListener onClickTabListener; private OnDoubleClickTabListener onDoubleClickTabListener; private List<View> tabViews; private boolean disableTensileSlidingBlock; // ?? private TabViewFactory tabViewFactory; private Paint bottomLinePaint; private int bottomLineColor = -1; private int bottomLineHeight = -1; private final PageChangedListener pageChangedListener = new PageChangedListener(); private final TabViewClickListener tabViewClickListener = new TabViewClickListener(); private final SetSelectedTabListener setSelectedTabListener = new SetSelectedTabListener(); private final DoubleClickGestureDetector tabViewDoubleClickGestureDetector; public PagerSlidingTabStrip(Context context) { this(context, null); } public PagerSlidingTabStrip(Context context, AttributeSet attrs) { super(context, attrs); setHorizontalScrollBarEnabled(false); //?????? removeAllViews(); if (attrs != null) { TypedArray attrsTypedArray = context.obtainStyledAttributes(attrs, R.styleable.PagerSlidingTabStrip); if (attrsTypedArray != null) { allowWidthFull = attrsTypedArray.getBoolean(R.styleable.PagerSlidingTabStrip_allowWidthFull, false); slidingBlockDrawable = attrsTypedArray.getDrawable(R.styleable.PagerSlidingTabStrip_slidingBlock); disableViewPager = attrsTypedArray.getBoolean(R.styleable.PagerSlidingTabStrip_disableViewPager, false); disableTensileSlidingBlock = attrsTypedArray .getBoolean(R.styleable.PagerSlidingTabStrip_disableTensileSlidingBlock, false); bottomLineColor = attrsTypedArray.getColor(R.styleable.PagerSlidingTabStrip_bottomLineColor, -1); bottomLineHeight = (int) attrsTypedArray .getDimension(R.styleable.PagerSlidingTabStrip_bottomLineHeight, -1); attrsTypedArray.recycle(); } } tabViewDoubleClickGestureDetector = new DoubleClickGestureDetector(context); getViewTreeObserver().addOnGlobalLayoutListener(setSelectedTabListener); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (allowWidthFull && tabsLayout != null) { View childView; for (int w = 0, size = tabsLayout.getChildCount(); w < size; w++) { childView = tabsLayout.getChildAt(w); ViewGroup.LayoutParams params = childView.getLayoutParams(); params.width = ViewGroup.LayoutParams.WRAP_CONTENT; childView.setLayoutParams(params); } } super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (!allowWidthFull) { return; } ViewGroup tabsLayout = getTabsLayout(); if (tabsLayout == null) { return; } if (tabsLayout.getChildCount() <= 0) { return; } if (tabViews == null) { tabViews = new ArrayList<View>(); } else { tabViews.clear(); } for (int w = 0; w < tabsLayout.getChildCount(); w++) { tabViews.add(tabsLayout.getChildAt(w)); } adjustChildWidthWithParent(tabViews, getMeasuredWidth() - tabsLayout.getPaddingLeft() - tabsLayout.getPaddingRight(), widthMeasureSpec, heightMeasureSpec); super.onMeasure(widthMeasureSpec, heightMeasureSpec); } /** * views?ViewView?parentViewWidth * * @param views ?View? * @param parentViewWidth Vie * @param parentWidthMeasureSpec View * @param parentHeightMeasureSpec View */ private void adjustChildWidthWithParent(List<View> views, int parentViewWidth, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { // ?View? for (View view : views) { if (view.getLayoutParams() instanceof MarginLayoutParams) { LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) view.getLayoutParams(); parentViewWidth -= lp.leftMargin + lp.rightMargin; } } // ?View??? int averageWidth = parentViewWidth / views.size(); int bigTabCount = views.size(); while (true) { Iterator<View> iterator = views.iterator(); while (iterator.hasNext()) { View view = iterator.next(); if (view.getMeasuredWidth() > averageWidth) { parentViewWidth -= view.getMeasuredWidth(); bigTabCount--; iterator.remove(); } } if (bigTabCount <= 0) { break; } averageWidth = parentViewWidth / bigTabCount; boolean end = true; for (View view : views) { if (view.getMeasuredWidth() > averageWidth) { end = false; } } if (end) { break; } } // ??View for (View view : views) { if (view.getMeasuredWidth() < averageWidth) { LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) view.getLayoutParams(); layoutParams.width = averageWidth; view.setLayoutParams(layoutParams); // ?? if (layoutParams instanceof MarginLayoutParams) { measureChildWithMargins(view, parentWidthMeasureSpec, 0, parentHeightMeasureSpec, 0); } else { measureChild(view, parentWidthMeasureSpec, parentHeightMeasureSpec); } } } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (bottomLineColor != -1 && bottomLineHeight != -1) { if (bottomLinePaint == null) { bottomLinePaint = new Paint(); bottomLinePaint.setColor(bottomLineColor); } canvas.drawRect(0, getBottom() - bottomLineHeight, getRight(), getBottom(), bottomLinePaint); } if (disableViewPager) return; /* ? */ ViewGroup tabsLayout = getTabsLayout(); if (tabsLayout != null && tabsLayout.getChildCount() > 0 && slidingBlockDrawable != null) { View currentTab = tabsLayout.getChildAt(currentPosition); if (currentTab != null) { float slidingBlockLeft = currentTab.getLeft(); float slidingBlockRight = currentTab.getRight(); if (currentPositionOffset > 0f && currentPosition < tabsLayout.getChildCount() - 1) { View nextTab = tabsLayout.getChildAt(currentPosition + 1); if (nextTab != null) { final float nextTabLeft = nextTab.getLeft(); final float nextTabRight = nextTab.getRight(); slidingBlockLeft = (currentPositionOffset * nextTabLeft + (1f - currentPositionOffset) * slidingBlockLeft); slidingBlockRight = (currentPositionOffset * nextTabRight + (1f - currentPositionOffset) * slidingBlockRight); } } // ? if (disableTensileSlidingBlock) { int center = (int) (slidingBlockLeft + (slidingBlockRight - slidingBlockLeft) / 2); slidingBlockLeft = center - slidingBlockDrawable.getIntrinsicWidth() / 2; slidingBlockRight = center + slidingBlockDrawable.getIntrinsicWidth() / 2; } slidingBlockDrawable.setBounds((int) slidingBlockLeft, getHeight() - slidingBlockDrawable.getIntrinsicHeight(), (int) slidingBlockRight, getHeight()); slidingBlockDrawable.draw(canvas); } } } /** * ? */ private ViewGroup getTabsLayout() { if (tabsLayout == null) { if (getChildCount() > 0) { tabsLayout = (ViewGroup) getChildAt(0); } else { removeAllViews(); LinearLayout tabsLayout = new LinearLayout(getContext()); tabsLayout.setGravity(Gravity.CENTER_VERTICAL); this.tabsLayout = tabsLayout; addView(tabsLayout, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, Gravity.CENTER_VERTICAL)); } } return tabsLayout; } /** * ?tab */ public void reset() { if (tabViewFactory != null) { ViewGroup tabViewGroup = getTabsLayout(); tabViewFactory.addTabs(tabViewGroup, viewPager != null ? viewPager.getCurrentItem() : 0); setTabClickEvent(); } } private void setTabClickEvent() { ViewGroup tabViewGroup = getTabsLayout(); if (tabViewGroup != null && tabViewGroup.getChildCount() > 0) { //?tab?Pager for (int w = 0; w < tabViewGroup.getChildCount(); w++) { View itemView = tabViewGroup.getChildAt(w); itemView.setTag(w); itemView.setOnClickListener(tabViewClickListener); itemView.setOnTouchListener(tabViewDoubleClickGestureDetector); } } } /** * ?Tab * * @param position ? * @return TabView */ @SuppressWarnings("unused") public View getTab(int position) { if (tabsLayout != null && tabsLayout.getChildCount() > position) { return tabsLayout.getChildAt(position); } else { return null; } } /** * ? */ private void scrollToChild(int position, int offset) { ViewGroup tabsLayout = getTabsLayout(); if (tabsLayout != null && tabsLayout.getChildCount() > 0 && position < tabsLayout.getChildCount()) { View view = tabsLayout.getChildAt(position); if (view != null) { //X?? int newScrollX = view.getLeft() + offset - getLeftMargin(view); if (position > 0 || offset > 0) { newScrollX -= getWidth() / 2 - getOffset(view.getWidth()) / 2; } //?X??? if (newScrollX != lastScrollX) { lastScrollX = newScrollX; scrollTo(newScrollX, 0); } } } } private int getLeftMargin(View view) { ViewGroup.LayoutParams params = view.getLayoutParams(); if (params instanceof MarginLayoutParams) { MarginLayoutParams marginParams = (MarginLayoutParams) params; return marginParams.leftMargin; } return 0; } private int getRightMargin(View view) { ViewGroup.LayoutParams params = view.getLayoutParams(); if (params instanceof MarginLayoutParams) { MarginLayoutParams marginParams = (MarginLayoutParams) params; return marginParams.rightMargin; } return 0; } /** * ???? */ private int getOffset(int newOffset) { if (lastOffset < newOffset) { if (start) { lastOffset += 1; return lastOffset; } else { start = true; lastOffset += 1; return lastOffset; } } if (lastOffset > newOffset) { if (start) { lastOffset -= 1; return lastOffset; } else { start = true; lastOffset -= 1; return lastOffset; } } else { start = true; lastOffset = newOffset; return lastOffset; } } /** * ?TAB */ private void selectedTab(int newSelectedTabPosition) { ViewGroup tabsLayout = getTabsLayout(); if (newSelectedTabPosition > -1 && tabsLayout != null && newSelectedTabPosition < tabsLayout.getChildCount()) { for (int w = 0, size = tabsLayout.getChildCount(); w < size; w++) { View tabView = tabsLayout.getChildAt(w); tabView.setSelected(w == newSelectedTabPosition); } } } /** * ViewPager * * @param viewPager ViewPager */ public void setViewPager(ViewPager viewPager) { if (disableViewPager) return; this.viewPager = viewPager; this.viewPager.addOnPageChangeListener(pageChangedListener); setTabClickEvent(); requestLayout(); } /** * Page?? * * @param onPageChangeListener Page?? */ @SuppressWarnings("unused") public void setOnPageChangeListener(OnPageChangeListener onPageChangeListener) { this.onPageChangeListener = onPageChangeListener; } /** * ?? * * @param allowWidthFull true??Item? */ public void setAllowWidthFull(boolean allowWidthFull) { this.allowWidthFull = allowWidthFull; requestLayout(); } /** * ? */ @SuppressWarnings("unused") public void setSlidingBlockDrawable(Drawable slidingBlockDrawable) { this.slidingBlockDrawable = slidingBlockDrawable; requestLayout(); } /** * ?? */ @SuppressWarnings("unused") public Drawable getSlidingBlockDrawable() { return slidingBlockDrawable; } /** * ??? * * @param disableTensileSlidingBlock ??? */ @SuppressWarnings("unused") public void setDisableTensileSlidingBlock(boolean disableTensileSlidingBlock) { this.disableTensileSlidingBlock = disableTensileSlidingBlock; invalidate(); } /** * ?Tab */ public int getTabCount() { ViewGroup tabsLayout = getTabsLayout(); return tabsLayout != null ? tabsLayout.getChildCount() : 0; } /** * Tab? * * @param onClickTabListener Tab? */ @SuppressWarnings("unused") public void setOnClickTabListener(OnClickTabListener onClickTabListener) { this.onClickTabListener = onClickTabListener; } /** * TAB?? * * @param onDoubleClickTabListener TAB?? */ public void setOnDoubleClickTabListener(OnDoubleClickTabListener onDoubleClickTabListener) { this.onDoubleClickTabListener = onDoubleClickTabListener; } /** * ?ViewPager * * @param disableViewPager ?ViewPager */ @SuppressWarnings("unused") public void setDisableViewPager(boolean disableViewPager) { this.disableViewPager = disableViewPager; if (viewPager != null) { viewPager.removeOnPageChangeListener(onPageChangeListener); viewPager = null; } requestLayout(); } /** * TabView? * * @param tabViewFactory TabView? */ @SuppressWarnings("unused") public void setTabViewFactory(TabViewFactory tabViewFactory) { this.tabViewFactory = tabViewFactory; reset(); getViewTreeObserver().addOnGlobalLayoutListener(setSelectedTabListener); } /** * * * @param bottomLineColor */ @SuppressWarnings("unused") public void setBottomLineColor(int bottomLineColor) { this.bottomLineColor = bottomLineColor; if (bottomLinePaint != null) { bottomLinePaint.setColor(bottomLineColor); } postInvalidate(); } /** * * * @param bottomLineHeight */ @SuppressWarnings("unused") public void setBottomLineHeight(int bottomLineHeight) { this.bottomLineHeight = bottomLineHeight; postInvalidate(); } /** * Tab? */ public interface OnClickTabListener { void onClickTab(View tab, int index); } /** * Tab?? */ public interface OnDoubleClickTabListener { void onDoubleClickTab(View view, int index); } /** * TabView? */ public interface TabViewFactory { /** * tab * * @param parent View * @param currentItemPosition ?? */ void addTabs(ViewGroup parent, int currentItemPosition); } private class PageChangedListener implements ViewPager.OnPageChangeListener { @Override public void onPageSelected(int position) { selectedTab(position); if (onPageChangeListener != null) { onPageChangeListener.onPageSelected(position); } } @Override public void onPageScrolled(int nextPagePosition, float positionOffset, int positionOffsetPixels) { ViewGroup tabsLayout = getTabsLayout(); if (nextPagePosition < tabsLayout.getChildCount()) { View view = tabsLayout.getChildAt(nextPagePosition); if (view != null) { currentPosition = nextPagePosition; currentPositionOffset = positionOffset; scrollToChild(nextPagePosition, (int) (positionOffset * (view.getWidth() + getLeftMargin(view) + getRightMargin(view)))); invalidate(); } } if (onPageChangeListener != null) { onPageChangeListener.onPageScrolled(nextPagePosition, positionOffset, positionOffsetPixels); } } @Override public void onPageScrollStateChanged(int arg0) { if (onPageChangeListener != null) { onPageChangeListener.onPageScrollStateChanged(arg0); } } } private class TabViewClickListener implements OnClickListener { @Override public void onClick(View v) { int index = (Integer) v.getTag(); if (onClickTabListener != null) { onClickTabListener.onClickTab(v, index); } if (viewPager != null) { viewPager.setCurrentItem(index, true); } } } private class SetSelectedTabListener implements ViewTreeObserver.OnGlobalLayoutListener { @Override public void onGlobalLayout() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { getViewTreeObserver().removeGlobalOnLayoutListener(this); } else { getViewTreeObserver().removeOnGlobalLayoutListener(this); } ViewGroup tabViewGroup = getTabsLayout(); if (tabViewGroup != null) { currentPosition = viewPager != null ? viewPager.getCurrentItem() : 0; if (!disableViewPager) { scrollToChild(currentPosition, 0); selectedTab(currentPosition); } } } } private class DoubleClickGestureDetector extends GestureDetector.SimpleOnGestureListener implements OnTouchListener { private GestureDetector gestureDetector; private View currentView; public DoubleClickGestureDetector(Context context) { gestureDetector = new GestureDetector(context, this); gestureDetector.setOnDoubleTapListener(this); } @Override public boolean onDoubleTap(MotionEvent e) { if (onDoubleClickTabListener != null) { onDoubleClickTabListener.onDoubleClickTab(currentView, (Integer) currentView.getTag()); return true; } else { return false; } } @Override public boolean onTouch(View v, MotionEvent event) { currentView = v; return gestureDetector.onTouchEvent(event); } } }