/**
* @author on 2018/11/12.
* Describe: 自定義指示器
* 1.支持指示器與文字等長效果 ******;
* 2.支持指示器與指示器文本等長
* 3.支持選中調整文字的大小
* 4.鏈式調用 支持各種顏色的修改
* 5.支持三角指示器以及下劃線指示器效果
* 和TabLayout效果相類似
* 注意點:
* 確保一定在ViewPagerAdapter中返回了:標題字符串,否則 <addTextFromViewPager/> 會報空指針異常
*/
public class NiceViewPagerIndicator extends HorizontalScrollView {
public enum IndicatorType{
/**
* 與標題等長
*/
EQUAL_TAB,
/**
* 與文字等長
*/
EQUAL_TEXT,
/**
* 指示器絕對長度
*/
ABSOLUTE_LENGTH;
}
public enum IndicatorShape{
/**
* 線性
*/
LINEAR,
/**
* 三角
*/
TRIANGLE
}
/**
* 記錄當前指示器的左右的X軸座標
*/
private float mLineLeft;
private float mLineRight;
private NicePageChangeListener mNicePageChangeListener;
private ViewPager mViewPager;
private Context mContext;
private IndicatorType mIndicatorType = IndicatorType.EQUAL_TEXT;
private IndicatorShape mIndicatorShape = IndicatorShape.LINEAR;
/**
* 控件的高度
*/
private int mIndicatorHeight;
private int mIndicatorWidth;
/**
* 三角指示器------
*/
private Path mTrianglePath;
private int mTriangleWidth = 14;
private int mTriangleHeight = 6;
private Paint mTrianglePaint;
/**
* ScrollView下的唯一子佈局
* 承載容納文本控件的作用 -----
*/
private LinearLayout mLinearContainer;
/**
* 滑動過程中的選中操作指示器Index
*/
private int mSelectedIndex;
/**
* 滑動結束後停留的指示器Index
*/
private int mCurrentIndex;
/**
* ViewPager 滑動參數 0-1
*/
private float mCurrentPositionOffset;
/**
* 標示tab的個數
*/
private int mTabCount;
/**
* 默認控制器水平padding
*/
private static final int DEFAULT_TAB_HNL_PADDING = 20;
/**
* 水平方向上的文本控件Padding
*/
private int mTabHorizontalPadding = DEFAULT_TAB_HNL_PADDING;
/**
* 根據控件的多少設定是否需要等分:
* false ----- 設置標題平分(針對標題較 少) [標題不可滑動]
* true ----- 設置標題按一定的Padding鋪滿整個Linear 適應較多的標題 [標題可能可以滑動]
*/
private boolean isExpand = true;
private LinearLayout.LayoutParams wrapTabLayoutParams;
private LinearLayout.LayoutParams expandTabLayoutParams;
/*
* 指示器(被選中的tab下的短橫線)
* true:indicator與文字等長;false:indicator與整個tab等長
*/
private int mNormalTextColor = Color.BLACK;
private int mSelectedTextColor = Color.RED;
private int mNormalTextSize = 30;
private int mSelectedTextSize = 35;
/**
* 標識 HorizontalScrollView滑動的x點
*/
private int lastScrollX = 0;
/**
* 用於測量文字長度的畫筆
*/
private Paint mMeasureTextPaint;
private Paint mIndicatorPaint;
private int mIndicatorColor = Color.GREEN;
private int mIndicatorStrokeWidth = 10;
/**
* 在ABSOLUTE_LENGTH 指示器類型下,默認的指示器長度
*/
private int mIndicatorLength = 40;
/*
* scrollView整體滾動的偏移量,dp
*/
private int mScrollOffset = 100;
/*----------------- 使用者可根據需要進行參數的修正 契合自己項目的需求-------------------*/
/**
* 設置指示器的高度
* @param indicatorHeightDp dp爲單位
* @return
*/
public NiceViewPagerIndicator setIndicatorHeight(int indicatorHeightDp){
this.mIndicatorStrokeWidth = indicatorHeightDp;
return this;
}
/**
* 是否與文字等長 --------
* @return
*/
public NiceViewPagerIndicator setIndicatorLengthType(IndicatorType mIndicatorType){
this.mIndicatorType = mIndicatorType;
return this;
}
public NiceViewPagerIndicator setIndicatorShapeType(IndicatorShape indicatorShapeType){
this.mIndicatorShape = indicatorShapeType;
return this;
}
/**
* 設置文本指示器的水品左右水品padding
*/
public NiceViewPagerIndicator setIndicatorHorlPadding(int tabHorizontalPadding){
this.mTabHorizontalPadding = tabHorizontalPadding;
return this;
}
/**
* 設置是否平分佈局
* @param isExpand boolean
* @return
*/
public NiceViewPagerIndicator setIsExpand(boolean isExpand){
this.isExpand = isExpand;
return this;
}
public NiceViewPagerIndicator setIndicatorColor(int indicatorColor){
this.mIndicatorColor = indicatorColor;
return this;
}
/**
* 設置未選中的顏色
* @param colorID
* @return
*/
public NiceViewPagerIndicator setNormalTextColor(int colorID){
this.mNormalTextColor = colorID;
return this;
}
/**
* 設置選中的顏色
* @param colorID
* @return
*/
public NiceViewPagerIndicator setSelectedTextColor(int colorID){
this.mSelectedTextColor = colorID;
return this;
}
/**
* 設置爲選中的文字的顏色
* @param textNormalSp
* @return
*/
public NiceViewPagerIndicator setNormalTextSize(int textNormalSp){
this.mNormalTextColor = textNormalSp;
return this;
}
/**
* 設置已經選中的文字的顏色
* @param textSelectedSp
* @return
*/
public NiceViewPagerIndicator setSelectedTextSize(int textSelectedSp){
this.mSelectedTextSize = textSelectedSp;
return this;
}
/**
* 設置指示器的長度
* @param indicatorLength
* @return
*/
public NiceViewPagerIndicator setIndicatorLength(int indicatorLength){
this.mIndicatorLength = indicatorLength;
return this;
}
/**
* ----------------------------------構造方法開始---------------------------------
*/
public NiceViewPagerIndicator(Context context) {
this(context,null);
}
public NiceViewPagerIndicator(Context context, AttributeSet attrs) {
this(context,attrs,0);
}
public NiceViewPagerIndicator(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 初始化ViewPager監聽
mNicePageChangeListener = new NicePageChangeListener();
mContext = context;
setFillViewport(true);
// 允許ViewGroup執行onDraw()執行繪製操作 ---
setWillNotDraw(false);
}
/**
* ----------------------------------構造方法結束---------------------------------
*/
/**
* 屬性初始化完成了綁定ViewPager 並執行各項初始化操作
* @param viewPager
*/
public void setUpViewPager(@NonNull ViewPager viewPager){
this.mViewPager = viewPager;
// 在adapter爲空時拋出異常 解決後面報的空指針檢查
if (mViewPager.getAdapter() == null) {
throw new IllegalStateException("ViewPager does not have adapter instance.");
}
viewPager.addOnPageChangeListener(mNicePageChangeListener);
init();
initViews();
}
/**
* 進行初始化
*/
private void init() {
float density = getResources().getDisplayMetrics().density;
mSelectedTextSize = (int)(mSelectedTextSize * density);
mNormalTextSize = (int)(mNormalTextSize * density);
mTabHorizontalPadding = (int)(mTabHorizontalPadding * density);
mScrollOffset = (int)(mScrollOffset * density);
mIndicatorLength =(int)(mIndicatorLength * density);
mTriangleHeight = (int)(mTriangleHeight * density);
mTriangleWidth = (int)(mTriangleWidth * density);
addOnlyContainerChild();
defTextIndicatorParams();
initMeasureTextPaints();
initIndicatorPaints();
initTrianglePaint();
}
private void initTrianglePaint() {
mTrianglePaint = new Paint();
mTrianglePaint.setAntiAlias(true);
mTrianglePaint.setDither(true);
mTrianglePaint.setColor(Color.WHITE);
mTrianglePaint.setStyle(Paint.Style.FILL);
}
private void initMeasureTextPaints() {
/*
* 文字畫筆
*/
mMeasureTextPaint = new Paint();
mMeasureTextPaint.setAntiAlias(true);
mMeasureTextPaint.setStrokeWidth(mIndicatorStrokeWidth);
mMeasureTextPaint.setTextSize(mSelectedTextSize);
}
private void initIndicatorPaints() {
mIndicatorPaint = new Paint();
mIndicatorPaint.setColor(mIndicatorColor);
mIndicatorPaint.setStrokeCap(Paint.Cap.ROUND);
mIndicatorPaint.setStrokeWidth(mIndicatorStrokeWidth);
mIndicatorPaint.setAntiAlias(true);
mIndicatorPaint.setDither(true);
mIndicatorPaint.setStyle(Paint.Style.FILL);
}
/**
* 添加Horizontal 唯一子佈局
*/
private void addOnlyContainerChild() {
mLinearContainer = new LinearLayout(mContext);
mLinearContainer.setOrientation(LinearLayout.HORIZONTAL);
mLinearContainer.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
// 將唯一子佈局添加上去
addView(mLinearContainer);
}
/**
* 創建兩個Tab的LayoutParams,wrapTabLayoutParams --- 爲寬度包裹內容,控件較多
* expandTabLayoutParams --- 寬度等分父控件剩餘空間,控件較少
*/
private void defTextIndicatorParams() {
//寬度包裹內容
wrapTabLayoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
//寬度等分 這裏用途會更加廣泛一點----
expandTabLayoutParams = new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1.0f);
}
private void initViews() {
mSelectedIndex = mViewPager.getCurrentItem();
mCurrentIndex = mViewPager.getCurrentItem();
if (mViewPager.getAdapter() != null) {
mTabCount = mViewPager.getAdapter().getCount();
}
addTextFromViewPager();
//滾動scrollView
getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
getViewTreeObserver().removeGlobalOnLayoutListener(this);
} else {
getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
// 繪製好了完成滑動------
scrollToChild(mCurrentIndex, 0);
}
});
}
/**
* 將從綁定關係的ViewPager的文本屬性,添加到linear_container佈局中去
*
* 跳過編譯條件,滿足下面的條件
*/
@SuppressWarnings("ConstantConditions")
private void addTextFromViewPager(){
mLinearContainer.removeAllViews();
for (int i=0 ;i< mTabCount ;i++){
addTextTab(i, mViewPager.getAdapter().getPageTitle(i).toString());
}
updateTextTabStyle();
}
/**
* 按照選中的更新標題式樣
*/
private void updateTextTabStyle() {
for (int i=0;i<mTabCount;i++){
TextView textView = (TextView) mLinearContainer.getChildAt(i);
if ( i == mSelectedIndex){
// 以px傳遞
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX,mSelectedTextSize);
textView.setTextColor(mSelectedTextColor);
}else {
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX,mNormalTextSize);
textView.setTextColor(mNormalTextColor);
}
}
}
/**
* 添加文本Tab
* @param i
* @param pageTitle
*/
private void addTextTab(int i, String pageTitle) {
TextView textTab = new TextView(mContext);
textTab.setGravity(Gravity.CENTER);
textTab.setText(pageTitle);
// 暫時定爲紅色
textTab.setTextColor(mNormalTextColor);
textTab.setTextSize(mNormalTextSize);
// 設置左右的Padding
textTab.setPadding(mTabHorizontalPadding,0,mTabHorizontalPadding,0);
textTab.setOnClickListener(new TextClickSelectListener(i));
// 設置文本控件的佈局params方式
mLinearContainer.addView(textTab,isExpand?expandTabLayoutParams:wrapTabLayoutParams);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mIndicatorHeight = h;
mIndicatorWidth = w;
}
/**
* 繪製指示器 ----- 計算指示器的起點與中點進行繪製
* @param canvas
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mTabCount == 0){
return;
}
if (IndicatorShape.TRIANGLE == mIndicatorShape){
initTriangleLocation();
canvas.drawPath(mTrianglePath,mTrianglePaint);
return;
}
// 獲得控件的高度
final int height = getHeight();
float nextLeft;
float nextRight;
// 依據文字等長還是tab等長進行效果展示
switch (mIndicatorType){
case EQUAL_TAB:
View currentTab = mLinearContainer.getChildAt(mCurrentIndex);
mLineLeft = currentTab.getLeft();
mLineRight = currentTab.getRight();
if (mCurrentPositionOffset > 0f && mCurrentIndex < mTabCount - 1) {
View nextTab = mLinearContainer.getChildAt(mCurrentIndex + 1);
nextLeft = nextTab.getLeft();
nextRight = nextTab.getRight();
mLineLeft = mLineLeft + (nextLeft - mLineLeft) * mCurrentPositionOffset;
mLineRight = mLineRight + (nextRight - mLineRight) * mCurrentPositionOffset;
}
break;
case EQUAL_TEXT:
getTextLocation(mCurrentIndex);
mLineLeft = textLocation.left;
mLineRight = textLocation.right;
if (mCurrentPositionOffset > 0f && mCurrentIndex < mTabCount - 1) {
getTextLocation(mCurrentIndex + 1);
nextLeft = textLocation.left;
nextRight = textLocation.right;
mLineLeft = mLineLeft + (nextLeft - mLineLeft) * mCurrentPositionOffset;
mLineRight = mLineRight + (nextRight - mLineRight) * mCurrentPositionOffset;
}
break;
case ABSOLUTE_LENGTH:
getLinearLayoutWithLength(mCurrentIndex);
mLineLeft = textLocation.left;
mLineRight = textLocation.right;
if (mCurrentPositionOffset > 0f && mCurrentIndex < mTabCount-1){
getLinearLayoutWithLength(mCurrentIndex + 1);
nextLeft = textLocation.left;
nextRight = textLocation.right;
mLineLeft = mLineLeft + (nextLeft - mLineLeft) * mCurrentPositionOffset;
mLineRight = mLineRight + (nextRight - mLineRight) * mCurrentPositionOffset;
}
break;
default:break;
}
canvas.drawRect(mLineLeft, height - mIndicatorPaint.getStrokeWidth()/2, mLineRight, height, mIndicatorPaint);
}
/*
* ----------------- 準備繪製白色的三角形指示器 ---------
*/
private void initTriangleLocation() {
// 這裏有問題嘛
float centerX = getTriangleCenterX(mCurrentIndex);
if (mCurrentIndex < mTabCount-1){
float nextCenterX = getTriangleCenterX(mCurrentIndex + 1);
centerX = centerX + (nextCenterX - centerX) * mCurrentPositionOffset;
}
if (mTrianglePath == null){
mTrianglePath = new Path();
}
mTrianglePath.reset();
mTrianglePath.moveTo(centerX - mTriangleWidth/2,mIndicatorHeight);
mTrianglePath.lineTo(centerX + mTriangleWidth/2, mIndicatorHeight);
mTrianglePath.lineTo(centerX, mIndicatorHeight - mTriangleHeight);
mTrianglePath.close();
}
/**
* 獲得文本的中點
* @param position
*/
private float getTriangleCenterX(int position){
View child = mLinearContainer.getChildAt(position);
int left = child.getLeft();
int width = child.getWidth();
return left + width/2f;
}
/*
* ------------ 結束繪製白色的三角形指示器 ---------
*/
/**
* 獲得指定tab中,文字的left和right,線性指示器
*/
@SuppressWarnings("ConstantConditions")
private void getTextLocation(int position) {
View tab = mLinearContainer.getChildAt(position);
String tabText = mViewPager.getAdapter().getPageTitle(position).toString();
float textWidth = mMeasureTextPaint.measureText(tabText);
int tabWidth = tab.getWidth();
textLocation.left = tab.getLeft() + (int) ((tabWidth - textWidth) / 2);
textLocation.right = tab.getRight() - (int) ((tabWidth - textWidth) / 2);
}
/**
* 根據定長繪製指示器
* @param position
*/
private void getLinearLayoutWithLength(int position){
View child = mLinearContainer.getChildAt(position);
int childLeft = child.getLeft();
int childWidth = child.getWidth();
textLocation.left = (int)(childLeft + childWidth / 2f - mIndicatorLength/2f);
textLocation.right = (int)(childLeft + childWidth / 2f + mIndicatorLength/2f);
}
private LeftRight textLocation = new LeftRight();
class LeftRight {
int left, right;
}
/**
* 滑動HorizontalScrollView 滾動方法
* @param position 標示下標ID
* @param currentTextLength 當前文本長度
* 比較關鍵 ---- 執行scrollView的滑動效果
*
* 這是很清除的,當水平方向滑動,值爲正向左滑動
*/
private void scrollToChild(int position, int currentTextLength) {
// 如果文本長度爲空,或者平分模式則返回
if (mTabCount == 0 || isExpand){
return;
}
//getLeft():tab相對於父控件,即tabsContainer的left
View child = mLinearContainer.getChildAt(position);
int newScrollX = child.getLeft() + currentTextLength ;
//附加一個偏移量,防止當前選中的tab太偏左
//可以去掉看看是什麼效果
// 相當於在原來基礎上向右滑 mScrollOffset的距離,就達到了太左或者太有可以往中間靠的效果
if (position > 0 || currentTextLength > 0) {
newScrollX -= mScrollOffset;
}
if (newScrollX != lastScrollX) {
lastScrollX = newScrollX;
scrollTo(newScrollX, 0);
}
}
/**
* 重寫onClickListener,爲了能夠安全的將參數傳遞進來
*/
private class TextClickSelectListener implements View.OnClickListener{
private int selectedIndex;
TextClickSelectListener(int selectedIndex) {
this.selectedIndex = selectedIndex;
}
@Override
public void onClick(View v) {
// 設置文本的選中----
if (mViewPager != null){
mViewPager.setCurrentItem(selectedIndex);
}
}
}
/**
* 重寫ViewPager的onPageChangeListener 目的在於監聽滑動狀態,進行綁定
*/
private class NicePageChangeListener implements ViewPager.OnPageChangeListener{
@Override
public void onPageScrolled(int i, float v, int i1) {
mCurrentIndex = i;
mCurrentPositionOffset = v;
//HorizontalScrollView滾動
scrollToChild(i, (int) (v * mLinearContainer.getChildAt(i).getWidth()));
//invalidate後onDraw會被調用,繪製指示器
invalidate();
}
@Override
public void onPageSelected(int i) {
mSelectedIndex = i;
updateTextTabStyle();
}
@Override
public void onPageScrollStateChanged(int i) {
}
}
}
使用方法:
indicator = (NiceViewPagerIndicator) findViewById(R.id.niceIndicator2);
indicator.setIndicatorLengthType(NiceViewPagerIndicator.IndicatorType.EQUAL_TEXT)
.setIndicatorShapeType(NiceViewPagerIndicator.IndicatorShape.LINEAR)
.setIndicatorColor(Color.BLUE);
indicator.setUpViewPager(mUsbViewPager);
mUsbViewPager.addOnPageChangeListener(this);
```