編輯:關於Android編程
好久沒有更新過博客了,趁今天有空分享一個導航欄的自定義控件。有關此控件的demo相信在網上已經爛大街了,一搜一大把。
我現在只著重分享一些我認為比較難理解的知識點。整個控件的難點大概有三個
1、游標的繪制。
2、ViewPager監聽器的理解。
3、游標的移動。
本文將注重這三個方面重點分析。
先上Demo的最終效果
樣例Module,有四個java文件和兩個xml文件
總結一下此控件的主要知識點
1、ViewGroup繪制流程。
2、ViewPager的用法。
3、OnPageChangeListener接口的用法。
4、scrollTo方法的使用。
需要完整代碼,請看底部鏈接,謝謝!(^_^)。下面我直接講核心代碼。
(1)SlideTab繼承了HorizontalScrollView控件之後,咋們需要重寫onDraw方法。接下來需要看個圖了解SlideTab控件的內部組成
整個SlideTab控件就是由這三個類型的控件組成。假設SlideTab控件已經初始化完成了。第一次由系統開始調用onDraw方法。
/**
* @param canvas
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//獲取當前Tab的左右兩邊的橫坐標值
View currentTabView = horizontalContainer.getChildAt(currentPosition);
float currentTabLeftX = currentTabView.getLeft();
float currentTabRightX = currentTabView.getRight();
int childCount = horizontalContainer.getChildCount();
//ViewPager在滑動的過程中會重復調用onDraw方法。下面if語句的內容是用來計算游標的起點坐標和終點坐標
if (currentPositionOffset > 0f && currentPosition < childCount - 1) {
//獲取下一個Tab左右兩邊的橫坐標值
View nextTab = horizontalContainer.getChildAt(currentPosition + 1);
float nextTabLeftX = nextTab.getLeft();
float nextTabRightX = nextTab.getRight();
//計算起點
currentTabLeftX = (currentPositionOffset * nextTabLeftX + (1f - currentPositionOffset)
* currentTabLeftX);
//計算終點
currentTabRightX = (currentPositionOffset * nextTabRightX + (1f - currentPositionOffset)
* currentTabRightX);
}
//繪制下劃線
// drawUnderline(canvas, horizontalContainer);
//繪制指示器
drawIndicator(canvas, currentTabLeftX, currentTabRightX);
}
currentPosition變量的初始值為0。
第8行代碼View currentTabView = horizontalContainer.getChildAt(currentPosition);獲取到LinearLayout容器裡面第一個View(實際是TextView)視圖。
第9,10行分別得到View視圖的左上角坐標和右上角坐標。
第11行獲取LinearLayout容器中TextView控件的總數。
第13行的判斷語句。currentPositionOffset 這是一個記錄著當前頁面滑動過程中的偏移量(如果不理解先放下,後面再講)初始值為0,很明顯,currentPositionOffset 不大於 0f,if語句不成立,略過(if語句裡面的內容稍後再分析)。繼續往下走。
來到底28行執行drawIndicator方法,開始繪制游標。
(2)現在來解決此控件的第一個難點,游標的繪制。
這個游標實際上是一條線。要畫一條先就必須得確定兩點的坐標值(初中知識,兩點才能確定一條直線嘛)。
/**
* 繪制游標
*
* @param canvas SlideTab控件的畫板
* @param currentLeftX 標題控件的左坐標
* @param currentRightX 標題控件的右坐標
*/
private void drawIndicator(Canvas canvas, float currentLeftX, float currentRightX) {
float indicatorMiddle = (indicatorPaint.getStrokeWidth() / 2);
float indicatorY = getHeight() - indicatorMiddle;
canvas.drawLine(currentLeftX, indicatorY, currentRightX, indicatorY, indicatorPaint);
}
為了更好的理解這幾段代碼,還是畫個圖。下圖黑色框是LinearLayout容器,綠色框是游標。
綠色框高度的值就是indicatZ喎?/kf/ware/vc/" target="_blank" class="keylink">vclBhaW50LmdldFN0cm9rZVdpZHRoKCmho73Tz8K1xLmk1/e+zcrHvMbL48/CzbzX89PSwb2x37XEs8jJq7XjoaM8YnIgLz4NCjxpbWcgYWx0PQ=="這裡寫圖片描述" src="/uploadfile/Collfiles/20160721/20160721111549899.png" title="\" />
紅色橫線表示currentLeftX變量。
藍色橫線表示currentRightX變量。
紅色豎線表示indicatorMiddle變量。
粉色豎線表示indicatorY變量。
由第9,10行的計算方法得到
左邊橙色坐標點(currentLeftX,indicatorY)。
右邊橙色坐標點(currentRightX,indicatorY)。
在第11行調用canvas.drawLine方法(這是一個繪制直線的方法)繪制游標。這個方法的最後的參數indicatorPaint是一個畫筆(用來描述這個直線的狀態,例如顏色,寬度,直線末端是否圓角等等。)
以上的內容就是游標繪制的流程。
(3)繪制好游標之後如何讓導航欄控件跟隨著ViewPager的滑動而滑動呢?現在我們需要寫一個setViewPager方法,將(Fragment+ViewPager)與SlideTab控件關聯起來。這個方法是提供給用戶(使用你控件的程序猿)調用。他們只需要傳來一個ViewPager的實例和一個標題名稱數組即可完成此控件的調用。
/**
* @param viewPager 用戶傳進來的ViewPager
* @param titleString 標題名稱
*/
public void setViewPager(ViewPager viewPager, String[] titleString) {
this.viewPager = viewPager;
addTab(titleString);
//設置ViewPager監聽事件
viewPager.addOnPageChangeListener(new SlideTabPageViewListener());
}
/**
* 添加Tab
*
* @param titleString 標題數組
*/
private void addTab(String[] titleString) {
//清空所有控件
horizontalContainer.removeAllViews();
for (int i = 0; i < viewPager.getAdapter().getCount(); i++) {
//創建垂直容器,用來包裹住下面TextView
tabVerticalContainer = new LinearLayout(context);
tabVerticalContainer.setOrientation(LinearLayout.VERTICAL);
tabVerticalContainer.setHorizontalGravity(Gravity.CENTER_HORIZONTAL);
//設置點擊事件
tabVerticalContainer.setOnClickListener(new ViewPagerClickListener(i));
tabVerticalContainer.setVerticalGravity(Gravity.CENTER_VERTICAL);
//將垂直LinearLayout容器放入水平LinearLayout容器中
horizontalContainer.addView(tabVerticalContainer, isExtendTab ? expandedTabLayoutParams
: defaultTabLayoutParams);
if (titleString != null) {
//創建標題
TextView textViews = new TextView(context);
textViews.setText(titleString[i]);
textViews.setTextSize(14);
textViews.setTextColor(Color.parseColor("#000000"));
textViews.setSingleLine(true);
tabVerticalContainer.addView(textViews, textViewLayoutParams);
}
}
}
第8行是一個自定義方法。根據ViewPager的頁面總數,設置標題導航欄。邏輯比較簡單只是單純的堆代碼,咋們略過吧。
我們重點關注第10行代碼。ViewPager注冊了一個監聽事件的實例。此實例有三個回調方法用來監聽用戶對屏幕的滑動操作。具體詳情請往下看,(SlideTab控件的游標滑動與這個監聽事件有很大關系。)
(4)SlideTabPageViewListener是SlideTab控件的內部類,實現了ViewPager.OnPageChangeListener接口,這個接口必須實現3個方法。
現在來解決第2個難點。就是ViewPager的監聽事件。
public void onPageScrolled(int position, float positionOffset,int positionOffsetPixels)
當你滑動頁面的時候會調用此方法,在滑動停止之前,此方法回一直被調用。
position表示當前頁面的下標。例如你有三個選項卡,現在從第一頁滑動到第二頁的過程中,這個position的下標是0(下標從0開始),滑動到第二頁position的時候下標就變成1了。
positionOffset表示當前頁面偏移的百分比。這個參數我們待會就會用到。
positionOffsetPixels表示當前頁面偏移的像素,一般情況不用。
public void onPageScrollStateChanged(int state)
當ViewPager頁面的狀態被改變的時候會調用此方法。怎麼理解這句話呢?
1、假如你觸摸屏幕從第一頁滑動到第二頁這個過程中,會回調此方法(如果你手指一直處於滑動狀態此方法就會一直被調用),傳過來的state的值是1表示正在滑動。
2、滑動結束之後,會再次回調此方法,傳過來的state的值是2表示滑動結束了。
3、結束滑動之後如果沒有其他的滑動操作,會再次回調此方法,傳過來的state的值是0,表示ViewPager處理閒置狀態。
public void onPageSelected(int position)
此方法是從當前頁面滑動到另一個頁面才會調用,並且這個position是新頁面的下標。注意如果你從當前頁往下一頁滑動的過程中(不松手)又滑回原頁面,此方法不會調用。
以上關於ViewPager的OnPageChangeListener接口的方法詳情,有了這些基礎之後就比較好解釋SlideTabPageViewListener內部類。
(5)SlideTabPageViewListener內部類
/**
* ViewPager滾動監聽事件
*/
public class SlideTabPageViewListener implements ViewPager.OnPageChangeListener {
@Override
public void onPageScrolled(int position, float positionOffset,
int positionOffsetPixels) {
currentPosition = position;
currentPositionOffset = positionOffset;
scrollToCurrentPosition(position, (int) (positionOffset * (horizontalContainer)
.getChildAt(position).getWidth()));
//重新繪制onDraw方法
invalidate();
}
@Override
public void onPageScrollStateChanged(int state) {
}
@Override
public void onPageSelected(int position) {
}
}
最後,咋們來解決此控件的最後一個難題,如何控制游標的移動。這是我感覺最難講清楚的一部分。
假設咋們正在觸摸屏幕從第一頁滑動到第二頁,在這個過程中。以上的onPageScrolled方法會一直被調用。
第8,9行的代碼是更新當前最新的頁面下標(currentPosition )和頁面的偏移值(currentPositionOffset )。這兩個變量咋們已經在上面的onDraw方法中見過。
第10行調用scrollToCurrentPosition方法(這個方法很重要)。將當前的頁面下標(值為0)和當前View(TextView)的偏移值傳遞過去。具體代碼如下。
/*****
*
*
* @param position
* @param offset
*/
public void scrollToCurrentPosition(int position, int offset) {
int currentOffsetX = horizontalContainer.getChildAt(position).getLeft() + offset;
int startScrollX = currentOffsetX;
if (position > 0 || offset > 0) {
//remainOffset表示剩余偏移量
startScrollX = currentOffsetX - remainOffset;
}
//如果位移發生變化,則滑動
if (startScrollX != lastScrollX) {
//更新最後一次滑動的距離
lastScrollX = startScrollX;
//horizontalContainer控件開始滑動
scrollTo(startScrollX, 0);
}
}
第11行的語句成立。計算得到startScrollX這是horizontalContainer 容器實際的滑動偏移值。
第17行lastScrollX默認初始值為0,因此if語句也成立。
最終在21行開始滑動horizontalContainer容器。(需要注意的是如果startScrollX的值大於0則往左滑動,小於0往右滑動。)
緊接著調用invalidate()方法,其內部代碼又會回調咋們剛才所說的onDraw方法。在onDraw方法中,執行了前面的代碼後來到了剛才沒有講解的if語句,由於此時處於滑動狀態。currentPositionOffset和currentPosition的值肯定是成立的。
(6)我再貼一下onDraw方法中if語句的代碼。
if (currentPositionOffset > 0f && currentPosition < childCount - 1) {
//獲取下一個Tab左右兩邊的橫坐標值
View nextTab = horizontalContainer.getChildAt(currentPosition + 1);
float nextTabLeftX = nextTab.getLeft();
float nextTabRightX = nextTab.getRight();
//計算起點
currentTabLeftX = (currentPositionOffset * nextTabLeftX + (1f - currentPositionOffset)
* currentTabLeftX);
//計算終點
currentTabRightX = (currentPositionOffset * nextTabRightX + (1f - currentPositionOffset)
* currentTabRightX);
}
在第3行,由於滑動沒有結束,此時currentPosition 的值還是0,又因為currentPosition + 1,所以nextTab得到的是第二個TextView的值。
第4,5行獲取nextTab控件(實際就是TextView)左右兩邊的坐標。
第7,10行計算滑動過程中游標的起點和終點,也就是下圖中左右兩邊的紅點的坐標。
計算完畢之後。最後就是再次調用drawIndicator方法重新繪制游標。
至此有關SlideTab控件與ViewPager滑動的流程就走完了。謝謝(^_^)Y
四、結束
鑒於篇幅的關系,我就不再演示控件的使用了。各位可以下載下面的demo看看源碼。demo中的SlideTabDemonstration類是入口。
此demo有BUG在所難免。僅限於學習。希望能幫到各位。
在我們開發應用程序時,通常都會用到獲取手機聯系人信息這一十分常用的功能,最近項目裡也要實現此功能,想到以後的APP還十分可能還有此功能,就干脆把這個小功能放到一個類中去,
復制代碼 代碼如下:filePath = Environment.getExternalStorageDirectory().getAbsolutePath() + /A
本節引言: 本節帶來的是Android多媒體中的——MediaPlayer,我們可以通過這個API來播放音頻和視頻 該類是Androd
【Activity】 一個Activity是一個應用程序組件,提供一個屏幕,用戶可以用來交互為了完成某項任務,例如撥號、拍照、發送email、看地圖。每一個activi