自定義view實作viewpage訓示器其實并不難,需要讀者對java畫圖類了解一些即可。通過對viewPage滑動時傳遞出的一些參數稍加利用,根據這些參數在正确的位置畫個三角形就成了訓示器,就這麼so easy。下面貼代碼,注釋都在代碼中,大家自己看哦
package com.example.view;
import java.util.List;
import com.example.viewpagerindicator.R;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.CornerPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Paint.Style;
import android.support.v4.view.ViewPager;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.TextView;
/**
* viewpage訓示器
* @author 黃海
*
*/
public class ViewPagerIndicator extends LinearLayout {
private Paint mPaint;// 繪制三角形的畫筆
private Path mPath;// 用于繪制三角形的邊
private int mTriangleWidth;// 三角形的寬
private int mTriangleHeight;// 三角形的高
private static final float RADIO_TRIANGLE_WIDTH = 1 / 6F;// 用于設定三角形的寬和tab底邊的比例,用于螢幕适配
/**
* 三角形底邊的最大寬度
*/
private final int DIMENSION_TRIANGLE_WIDTH_MAX = (int) (getScreenWidth() / 3 * RADIO_TRIANGLE_WIDTH);
private int mInitTranslationX;// 第一個三角形初始化的偏移位置
private int mTranslationX;// 移動時候的三角形偏移位置
private int mTabVisibleCount;// 可見tab的數量
private static final int COUNT_DEFAULT_TAB = 4;// 預設可見tab為4個
private List<String> mTitles;// 接收傳遞過來的title
private static final int COLOR_TEXT_NORMAL = Color.parseColor("#FFFFFF");
private static final int COLOR_TEXT_HIGHLIGHT = Color
.parseColor("#FF4CDA0F");
public ViewPagerIndicator(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public ViewPagerIndicator(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray attributes = context.obtainStyledAttributes(attrs,
R.styleable.ViewPagerIndicator);
mTabVisibleCount = attributes.getInt(
R.styleable.ViewPagerIndicator_visible_tab_count,
COUNT_DEFAULT_TAB);
if (mTabVisibleCount < 0) {
mTabVisibleCount = COUNT_DEFAULT_TAB;
}
// 用完必須釋放
attributes.recycle();
// 初始化畫筆
mPaint = new Paint();
// 防止邊緣鋸齒
mPaint.setAntiAlias(true);
mPaint.setColor(Color.parseColor("#ffffff"));
mPaint.setStyle(Style.FILL);
mPaint.setPathEffect(new CornerPathEffect(2));
}
public ViewPagerIndicator(Context context) {
this(context, null);
}
/**
* 繪制三角形
* 繪制VIew本身的内容,通過調用View.onDraw(canvas)函數實作,繪制自己的孩子通過dispatchDraw(canvas)實作
*
* 畫完背景後,draw過程會調用onDraw(Canvas canvas)方法,然後就是dispatchDraw(Canvas canvas)方法,
* dispatchDraw
* ()主要是分發給子元件進行繪制,我們通常定制元件的時候重寫的是onDraw()方法。值得注意的是ViewGroup容器元件的繪制
* ,當它沒有背景時直接調用的是dispatchDraw
* ()方法,而繞過了draw()方法,當它有背景的時候就調用draw()方法,而draw()方法裡包含了
* dispatchDraw()方法的調用。是以要在ViewGroup上繪制東西的時候往往重寫的是
* dispatchDraw()方法而不是onDraw()方法,或者自定制一個Drawable,重寫它的draw(Canvas c)和
* getIntrinsicWidth(),getIntrinsicHeight()方法,然後設為背景
*/
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
/**
* save:用來儲存Canvas的狀态。save之後,可以調用Canvas的平移、放縮、旋轉、錯切、裁剪等操作。
* restore:用來恢複Canvas之前儲存的狀态。防止save後對Canvas執行的操作對後續的繪制有影響。
* save和restore要配對使用(restore可以比save少,但不能多),如果restore調用次數比save多,會引發Error。
*/
canvas.save();
canvas.translate(mInitTranslationX + mTranslationX, getHeight());
canvas.drawPath(mPath, mPaint);
canvas.restore();
}
/**
* 設定三角形的大小
*
* onSizeChanged()在控件大小發生變化的時候調用(例如第一次初始化控件的時候) 布局過程中,
* 先調onMeasure計算每個child的大小, 然後調用onLayout對child進行布局,
* onSizeChanged()是在布局發生變化時的回調函數,間接回去調用onMeasure, onLayout函數重新布局
* onSizeChanged的啟動時間在onDraw之前
*/
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// w/3為每個tab的寬度,目前可見為3個
mTriangleWidth = (int) (w / mTabVisibleCount * RADIO_TRIANGLE_WIDTH);
mTriangleWidth = Math.min(mTriangleWidth, DIMENSION_TRIANGLE_WIDTH_MAX);
// 第一個三角形的偏移位置
mInitTranslationX = w / mTabVisibleCount / 2 - mTriangleWidth / 2;
initTriangle();
}
/**
* 初始化三角形
*/
private void initTriangle() {
// 将三角形角度設定為30度
mTriangleHeight = (int) (mTriangleWidth / 2 * Math.tan(Math.PI / 6));
mPath = new Path();
mPath.moveTo(0, 0);
mPath.lineTo(mTriangleWidth, 0);
mPath.lineTo(mTriangleWidth / 2, -mTriangleHeight);
// 關閉目前輪廓,完成閉合
mPath.close();
}
/**
* 三角形跟随ViewPager移動
*
* @param position
* @param positionOffset
*/
public void scroll(int position, float positionOffset) {
int tabWidth = getWidth() / mTabVisibleCount;
mTranslationX = (int) (tabWidth * (positionOffset + position));
/**
* 容器移動,在tab處于移動至最後一個時
*/
// if (position > =(mTabVisibleCount - 2) && positionOffset > 0
// && getChildCount() > mTabVisibleCount) {
if (position > (mTabVisibleCount - 2) && positionOffset > 0
&& getChildCount() > mTabVisibleCount) {
if (mTabVisibleCount != 1) {
// this.scrollTo((position - (mTabVisibleCount - 2)) * tabWidth
// + (int) (tabWidth * positionOffset), 0);
this.scrollTo((int) (tabWidth * (positionOffset + position
- mTabVisibleCount + 1)), 0);
} else {
this.scrollTo(position * tabWidth
+ (int) (tabWidth * positionOffset), 0);
}
}
invalidate();
}
/**
* xml加載完成之後,回調此方法
*
* 設定每個tab的LayoutParams
*/
@Override
protected void onFinishInflate() {
super.onFinishInflate();
int childCount = getChildCount();
if (childCount == 0) {
return;
}
for (int i = 0; i < childCount; i++) {
View view = getChildAt(i);
LinearLayout.LayoutParams params = (LayoutParams) view
.getLayoutParams();
params.weight = 0;
params.width = getScreenWidth() / mTabVisibleCount;
view.setLayoutParams(params);
}
setItemClickEvent();
}
/**
* 擷取螢幕的寬度
*
* @return
*/
private int getScreenWidth() {
WindowManager wm = (WindowManager) getContext().getSystemService(
Context.WINDOW_SERVICE);
DisplayMetrics outMetrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(outMetrics);
return outMetrics.widthPixels;
}
/**
* 動态設定tab的數量
*
* @param count
*/
public void setVisibleTabCount(int count) {
mTabVisibleCount = count;
}
/**
* 動态設定tab
*
* @param titles
*/
public void setTabItemTitles(List<String> titles) {
if (titles != null && titles.size() > 0) {
this.removeAllViews();
mTitles = titles;
for (String title : mTitles) {
this.addView(generateTextView(title));
}
setItemClickEvent();
}
}
/**
* 根據title建立tab
*
* @param title
* @return
*/
private View generateTextView(String title) {
TextView textView = new TextView(getContext());
LinearLayout.LayoutParams params = new LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
params.width = getScreenWidth() / mTabVisibleCount;
textView.setText(title);
textView.setGravity(Gravity.CENTER);
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16);
textView.setTextColor(COLOR_TEXT_NORMAL);
textView.setLayoutParams(params);
return textView;
}
// 接收關聯的ViewPager
private ViewPager mViewPager;
/**
* 提供一個接口供外部ViewPager使用
*
* @author Administrator
*
*/
public interface PageOnChangeListener {
public void onPageScrolled(int position, float positionOffset,
int positionOffsetPixels);
public void onPageSelected(int position);
public void onPageScrollStateChanged(int state);
}
public PageOnChangeListener mListener;
public void setViewPagerOnPageChangeListener(PageOnChangeListener listener) {
mListener = listener;
}
/**
* 設定關聯的ViewPager
*
* @param viewpager
* @param position
*/
public void setViewPager(ViewPager viewpager, int position) {
mViewPager = viewpager;
mViewPager.addOnPageChangeListener(new OnPageChangeListener() {
@Override
public void onPageSelected(int position) {
if (mListener != null) {
mListener.onPageSelected(position);
}
highLightTextView(position);
}
@Override
public void onPageScrolled(int position, float positionOffset,
int positionOffsetPixels) {
// 三角形跟随ViewPager移動的距離就是:
scroll(position, positionOffset);
if (mListener != null) {
mListener.onPageScrolled(position, positionOffset,
positionOffsetPixels);
}
}
@Override
public void onPageScrollStateChanged(int state) {
if (mListener != null) {
mListener.onPageScrollStateChanged(state);
}
}
});
mViewPager.setCurrentItem(position);
highLightTextView(position);
}
/**
* 高亮被點選的tab
*
* @param position
*/
private void highLightTextView(int position) {
resetTextViewColor();
View view = getChildAt(position);
if (view instanceof TextView) {
((TextView) view).setTextColor(COLOR_TEXT_HIGHLIGHT);
}
}
/**
* 重置tab文本顔色
*/
private void resetTextViewColor() {
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);
if (view instanceof TextView) {
((TextView) view).setTextColor(COLOR_TEXT_NORMAL);
}
}
}
/**
* 設定Tab的點選事件
*/
private void setItemClickEvent() {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final int j = i;
View view = getChildAt(i);
view.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mViewPager.setCurrentItem(j);
}
});
}
}
}
訓示器的一個自定義屬性
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="visible_tab_count" format="integer" />
<declare-styleable name="ViewPagerIndicator">
<attr name="visible_tab_count" />
</declare-styleable>
</resources>
然後例子的activity
package com.example.viewpagerindicator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.example.view.ViewPagerIndicator;
import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.view.Menu;
import android.view.MenuItem;
import android.view.Window;
public class MainActivity extends FragmentActivity {
private ViewPager mViewpager;
private ViewPagerIndicator mViewPagerIndicator;
private List<String> mTitles = Arrays.asList("短信1", "收藏2", "推薦3", "短信4",
"收藏5", "推薦6", "短信7", "收藏8", "推薦9");
private List<VpSimpleFragment> mContents = new ArrayList<VpSimpleFragment>();// 裝載ViewPager資料的List
/**
* FragmentPagerAdapter,見名知意,這個擴充卡就是用來實作Fragment在ViewPager裡面進行滑動切換的,是以,
* 如果我們想實作Fragment的左右滑動,可以選擇ViewPager和FragmentPagerAdapter實作。
* FragmentPagerAdapter擁有自己的緩存政策
* ,當和ViewPager配合使用的時候,會緩存目前Fragment以及左邊一個、右邊一個,一共三個Fragment對象。
* 假如有三個Fragment
* ,那麼在ViewPager初始化之後,3個fragment都會加載完成,中間的Fragment在整個生命周期裡面隻會加載一次
* ,當最左邊的Fragment處于顯示狀态
* ,最右邊的Fragment由于超出緩存範圍,會被銷毀,當再次滑到中間的Fragment的時候,最右邊的Fragment會被再次初始化。
*/
private FragmentPagerAdapter mAdapter;// ViewPager擴充卡
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
initViews();
initDatas();
// 動态設定tab
mViewPagerIndicator.setVisibleTabCount(3);
mViewPagerIndicator.setTabItemTitles(mTitles);
mViewpager.setAdapter(mAdapter);
mViewPagerIndicator.setViewPager(mViewpager, 0);
}
/**
* 初始化視圖
*/
private void initDatas() {
mViewpager = (ViewPager) findViewById(R.id.viewpager);
mViewPagerIndicator = (ViewPagerIndicator) findViewById(R.id.indicator);
}
/**
* 初始化資料
*/
private void initViews() {
for (String title : mTitles) {
VpSimpleFragment fragment = VpSimpleFragment.newInstance(title);
mContents.add(fragment);
}
mAdapter = new FragmentPagerAdapter(getSupportFragmentManager()) {
@Override
public int getCount() {
return mContents.size();
}
@Override
public Fragment getItem(int position) {
return mContents.get(position);
}
};
}
}
xml檔案
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.example.viewpagerindicator"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffffff"
android:orientation="vertical" >
<com.example.view.ViewPagerIndicator
android:id="@+id/indicator"
android:layout_width="match_parent"
android:layout_height="45dp"
android:background="@drawable/shape_backgroud"
android:orientation="horizontal"
app:visible_tab_count="4" >
</com.example.view.ViewPagerIndicator>
<android.support.v4.view.ViewPager
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" >
</android.support.v4.view.ViewPager>
</LinearLayout>
整體難度中等,大家如果有類似的需求可以直接copy ViewPageIndicator的代碼拿去使用,不用謝。
源碼