編輯:關於android開發
首先帶大家看一下實現效果,用了兩種實現方式:
1.基於LinearLayout實現,導航欄不可響應手指滑動
2.基於HorizontalScrollView實現,導航欄可響應手指滑動
實現方式雖然不一樣,但是使用的是一樣的,因為我接口封裝的一模一樣,下面看實現效果。
基於LinearLayout的實現:
基於HorizontalScrollView的實現:
兩者效果一樣,區別就在於導航條可否隨用戶操作滑動。
下面只說明LinearLayout實現,HorizontalScrollView僅僅是套了一層LinearLayout,因為它只允許套一個子控件。
需求開發點:
1.自定義控件的編寫(通用方法:構造函數、init()、onMeasure、onLayout、onDraw、dispatchDraw)
2.白色圓點的偏移量的計算
3.當Tab頁超過5個的時候,白點和LinearLayout內容的組合滑動,達到白點不動,而文字移動的效果。
實現這三點,這個控件就算完事,當然一些細節方面的,可以根據自己的需求去調優。
1.自定義控件的編寫
1) 這裡主要是onMeasure方法的復寫,不能用默認的onMeasure,需要我們根據Tab的數量去計算,下圖是1-6個Tab的measure結果
這個就是根據Tab的數量平分屏幕的寬,當數量超過5個的時候,為了保持效果,只顯示5個,超出的內容將其追加到屏幕外面,屏幕是有界的,視圖是無界的,你可以無限往右邊添加,只要內存能容下,可以用個for循環無限添加看看發生什麼。
2) 復寫dispatchDraw方法,這裡dispatchDraw方法的作用就是畫一個白色的圓點。
白色圓點的偏移量的計算
這個得靠ViewPager的OnPageChangeListener,通過它onPageScrolled的回調去計算,計算好了調用invalidate()去重繪。
當Tab頁超過5個的時候,白點和LinearLayout內容的組合滑動,達到白點不動,而文字移動的效果。
當滑動到第5個Tab,如果只是白色圓點移動就會移動到屏幕外面去,這個時候也需要讓LinearLayout內容聯動,通過scrollTo方法,往與白點相反的方向移動內容,使其和白點的移動達到平衡,這樣白點不動,上面的標題內容移動。
下面分析,寫該控件需要的一些變量,先看截圖:
1.需要白點是吧,我們用畫筆畫,所以Paint需要
2.白點畫多大?所以需要一個白點的半徑變量radius
3.白點的偏移量,我們定義一個變量mOffset,根據ViewPager的滑動動態計算
4.上面還有一排文字,用TextView實現,這個TextView是根據Tab的數量動態添加的,數量不可控,我這裡並沒有定義成變量。但是文字內容對應的是一個Fragment的標題,也就是說標題和Fragment是一一對應的關系。OK用Map。
/**
* 存放Tab的集合 Key:導航欄的標題 Fragment:導航頁
*/
private Map mTabs;
TextView就是根據mTabs的數量動態添加的。
5.我們怎麼去計算滑動偏移量呢?這個得靠ViewPager的OnPageChangeListener,通過它onPageScrolled的回調去計算mOffset,就是通過這種計算圓點的偏移,這裡我持有了一個ViewPager的引用,所以有變量mViewPager,其實要不要關系不大.
6.屏幕最大可顯示的Tab數量MAX_VISIBLE_TAB_COUNTS,總不能有100個你就顯示100個吧.
7.實際屏幕上可顯示的Tab數量mRealVisibleTabCounts
8.屏幕寬度mScreenWidth輔助onMeasure計算的
下面是實現代碼:
package com.csm.hwtab;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Cap;
import android.graphics.Paint.Style;
import android.support.v4.app.Fragment;
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.View.OnClickListener;
import android.widget.LinearLayout;
import android.widget.TextView;
/**
* @author Rander.C
*/
@SuppressLint("NewApi")
public class TabLinearLayout extends LinearLayout implements OnClickListener, OnPageChangeListener {
/**
* 最大可顯示的Tab數,可寫成自定義屬性在xml裡面配置
*/
private static final int MAX_VISIBLE_TAB_COUNTS = 5;
/**
* 實際可見的Tab數,比如有8個Tab,超過8個,為了保證效果,最多顯示5個
* 此時mRealVisibleTabCounts為5
* 如果只有3個tab小於5個,則mRealVisibleTabCounts為3
* 並根據這個去計算每個tab的寬度,屏幕寬度除以mRealVisibleTabCounts嘛
*/
private int mRealVisibleTabCounts;
/**
* 小圓點滑動偏移量
*/
private float mOffset;
/**
* 繪制小圓點畫筆
*/
private Paint mPaint;
/**
* 小圓點半徑
*/
private int mCircleRadius;
/**
* 屏幕寬度
*/
private int mScreenWidth;
/**
* 存放Tab的集合 Key:導航欄的標題 Fragment:導航頁
*/
private Map mTabs;
/**
* View中有getContext()方法,用變量就會
* 多一個Context引用,會增加一丁點虛擬機的負擔嗎?
* 如果不用變量,但是每次都使用getContext(),是不是都要多一層
* 調用棧,這個怎麼權衡,求答案,還是我想多了?
*/
private Context mContext;
private ViewPager mViewPager;
public TabLinearLayout(Context context, AttributeSet attrs) {
this(context, attrs, -1);
}
public TabLinearLayout(Context context) {
this(context, null);
}
public TabLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mContext = getContext();
setOrientation(HORIZONTAL);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setStyle(Style.FILL);
mPaint.setColor(Color.WHITE);
mPaint.setStrokeCap(Cap.ROUND);
DisplayMetrics metrics = getResources().getDisplayMetrics();
mScreenWidth = metrics.widthPixels;
mCircleRadius = (int) getResources().getDimension(R.dimen.radius);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int tabCount = getChildCount();
if (tabCount == 0) {
return;
}
/** 不管默認測量結果怎麼樣,我們都要重新給其寬度賦值*/
mRealVisibleTabCounts = MAX_VISIBLE_TAB_COUNTS < tabCount ? MAX_VISIBLE_TAB_COUNTS : tabCount;
// 為每一個子view重新分配mScreenWidth / mRealVisibleTabCounts大小的寬度
int averageWidth = mScreenWidth / mRealVisibleTabCounts;
for (int i = 0; i < tabCount; i++) {
View view = getChildAt(i);
LinearLayout.LayoutParams params = (android.widget.LinearLayout.LayoutParams) view.getLayoutParams();
params.weight = 0;
params.width = averageWidth;
view.setLayoutParams(params);
}
}
/**
* 獲得Tabs列表
* @return
*/
public List getTabs() {
return new ArrayList(mTabs.values());
}
/**
* 添加一個Tab項,添加一個tab的時候同時給
* 該視圖添加一個TextView
* @param title
* @param tab
*/
public void addTab(String title, Fragment tab) {
if (mTabs == null) {
mTabs = new LinkedHashMap<>();
}
mTabs.put(title, tab);
addView(createTabItem(title, mTabs.size() - 1));
}
/**
* 設置ViewPager
* 同時給其設置OnPageChangeListener監聽器。
* @param viewPager
*/
public void setViewPager(ViewPager viewPager) {
mViewPager = viewPager;
mViewPager.setOnPageChangeListener(this);
}
/**
* 創建一個Tab視圖
* @param title Fragment對應的標題
* @param index 該Fragment對應的索引下標
* @return 已經構造好的tab視圖
*/
private View createTabItem(String title, int index) {
TextView tv_title = new TextView(mContext);
tv_title.setText(title);
tv_title.setTag(index);
tv_title.setGravity(Gravity.CENTER);
tv_title.setTextColor(Color.WHITE);
tv_title.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18);
tv_title.setOnClickListener(this);
return tv_title;
}
@Override
public void onClick(View v) {
int tabIndex = (Integer) v.getTag();
if (onTabOperatorListener != null) {
onTabOperatorListener.onTabClick(tabIndex);
}
mViewPager.setCurrentItem(tabIndex);
}
@Override
public void onPageScrollStateChanged(int state) {
if (null != onTabOperatorListener) {
onTabOperatorListener.onPageScrollStateChanged(state);
}
}
@Override
public void onPageScrolled(int sourcePosition, float positionOffset, int positionOffsetPixels) {
mRealVisibleTabCounts = MAX_VISIBLE_TAB_COUNTS < getChildCount() ? MAX_VISIBLE_TAB_COUNTS : getChildCount();
int tabWidth = getWidth() / mRealVisibleTabCounts;
// 計算小圓點的偏移量,這個可以畫圖理解
mOffset = (int) (tabWidth / 2 + sourcePosition * tabWidth + positionOffset * tabWidth );
// 如果滾動到最後一個,則同時要向左滾動tab內容,這種情況是:原點在向右移動,同時整體又向做滑動,兩個滑動相互抵消,相當於原點沒有移動
if (getChildCount() > mRealVisibleTabCounts && positionOffset > 0
&& sourcePosition >= mRealVisibleTabCounts - 1) {
scrollTo((int) ((sourcePosition + 1 - mRealVisibleTabCounts) * tabWidth + tabWidth * positionOffset), 0);
}
invalidate();
if (null != onTabOperatorListener) {
onTabOperatorListener.onTabScrolled(sourcePosition, positionOffset, positionOffsetPixels);
}
}
@Override
public void onPageSelected(int index) {
if (null != onTabOperatorListener) {
onTabOperatorListener.onTabSelected(index);
}
/**
* 這裡不要多想,只是為了保證選中的tab為第一個時,讓布局重置到原點
*/
if(index == 0)
{
scrollTo(0, 0);
}
}
/**
* Tab操作相關的監聽器
* @author rander
*/
public interface OnTabOperatorListener {
void onTabClick(int tabIndex);
void onTabSelected(int tabIndex);
void onTabScrolled(int sourcePosition, float positionOffset, int positionOffsetPixels);
void onPageScrollStateChanged(int state);
}
public OnTabOperatorListener onTabOperatorListener;
public void setOnTabItemClickListener(OnTabOperatorListener onTabOperatorListener) {
this.onTabOperatorListener = onTabOperatorListener;
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
//如果只有一個Tab則,不繪制圓點
if (getChildCount() <= 1) {
return;
}
//根據mOffset繪制偏移量
canvas.drawCircle(mOffset, getHeight() - mCircleRadius * 2, mCircleRadius, mPaint);
}
}
關於偏移量的計算畫圖理解:
偏移量計算的表達式:
mOffset = (int) (tabWidth / 2 + sourcePosition * tabWidth + positionOffset * tabWidth );
圖解:
第一個豎線和第二個豎線:tabWidth / 2
第二個豎線和第三個豎線:sourcePosition * tabWidth
第三個豎線和第四個豎線:pZ喎?http://www.Bkjia.com/kf/ware/vc/" target="_blank" class="keylink">vc2l0aW9uT2Zmc2V0ICogdGFiV2lkdGg8YnIgLz4NCtK7z+C8077NysfGq9LGwb/ByyzV4rj20qrV0rW9c291cmNlUG9zaXRpb266zXBvc2l0aW9uT2Zmc2V0tcS55sLJvs26w7zGy+PGq9LGwb/By6Osc2Nyb2xsVG+1xMar0sbBv9Kyv8nS1LLO1dXV4rj2wO294qGjPC9wPg0KPHA+vdPPwsC0vs3Kx8q508PByy48c3Ryb25nPsfzTGludXjPwrXEusO1xLutzby5pL7fPC9zdHJvbmc+PC9wPg0KPHA+yunQtLK8vtZ0YWIueG1so7o8L3A+DQo8cHJlIGNsYXNzPQ=="brush:java;">
在Activity中使用:
package com.csm.hwtab;
import com.csm.hwtab.adapter.MyFragmentPagerAdapter;
import com.csm.hwtab.fragment.OneFragment;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.view.ViewPager;
import android.view.Window;
public class TestActivity extends FragmentActivity{
private ViewPager mViewPager;
private TabLinearLayout mTabview;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.tab);
initTabsView();
}
private void initTabsView() {
mViewPager = (ViewPager) findViewById(R.id.view_pager);
mTabview = (TabLinearLayout)findViewById(R.id.tab);
mTabview.addTab("先秦0", new OneFragment());
mTabview.addTab("先秦1", new OneFragment());
mTabview.addTab("先秦2", new OneFragment());
mTabview.addTab("先秦3", new OneFragment());
mTabview.addTab("先秦4", new OneFragment());
mTabview.addTab("先秦5", new OneFragment());
mTabview.addTab("先秦6", new OneFragment());
mViewPager.setAdapter(new MyFragmentPagerAdapter(getSupportFragmentManager(), mTabview.getTabs()));
mTabview.setViewPager(mViewPager);
mViewPager.setCurrentItem(0);
}
}
效果就如同上面的效果一樣了。
github:https://github.com/shuangmin/HwTab
01-安卓環境初搭建,01-安卓環境搭建需要的軟件如沒有提供下載鏈接,文章中會注明! 首先下載 eclipse 安裝!(筆者提供的鏈接中沒有Eclipse下載!) 安裝完
Linux內核系列—9.操作系統開發之Loader,linuxloader一個操作系統從開機到開始運行,大致經歷“引導—>加載內核入內存&m
Android For JNI(二)——C語言中的數據類型,輸出,輸入函數以及操作內存地址,內存修改器 Android For JNI(二)—&mdash
2015 Android Dev Summit(安卓開發峰會)第一天,androidsummit今年的Google I/O沒有抽到票,不能到現場參加。不過11月舉行的An