最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Android ViewPager源碼詳細(xì)分析

 更新時(shí)間:2017年02月25日 11:47:11   作者:Jaivne_Kuang  
這篇文章主要為大家詳細(xì)介紹了Android ViewPager源碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下

1.問題

由于Android Framework源碼很龐大,所以讀源碼必須帶著問題來讀!沒有問題,創(chuàng)造問題再來讀!否則很容易迷失在無數(shù)的方法與屬性之中,最后無功而返。

那么,關(guān)于ViewPager有什么問題呢?
1). setOffsreenPageLimit()方法是如何實(shí)現(xiàn)頁面緩存的?
2). 在布局文件中,ViewPager布局內(nèi)部能否添加其他View?
3). 為什么ViewPager初始化時(shí),顯示了一個(gè)頁面卻不會(huì)觸發(fā)onPageSelected回調(diào)?

問題肯定不止這三個(gè),但是有這三個(gè)問題基本可以找到本次分析的重點(diǎn)了。讀者朋友也可以自己先提出一些問題,再看下面的分析,看看是否可以從分析過程中找到答案。

2.從onMeasure()下手

ViewPager繼承自ViewGroup,是Android Framework提供的一個(gè)控件,而Android系統(tǒng)顯示控件的流程就是: Activity加載布局實(shí)例化所有控件 —> rootView遍歷所以控件 —> 對(duì)需要重繪的控件執(zhí)行測(cè)量,布局,繪制的操作。

而轉(zhuǎn)化到某個(gè)控件來說,它的流程就是:構(gòu)造方法 —> onMeasure —> onLayout —> onDraw
由于ViewPager的構(gòu)造方法中只是初始化了一些與本文主題無關(guān)的屬性就略過不講,那么自然而然onMeasure方法就來到了我們眼前。

那么在onMeasure中ViewPager做了些什么呢?先把源碼擺出來,我進(jìn)行了一些刪減。

@Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    //測(cè)量ViewPager自身大小
    setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
        getDefaultSize(0, heightMeasureSpec));

    final int measuredWidth = getMeasuredWidth();

    // child的寬高,占滿父控件
    int childWidthSize = measuredWidth - getPaddingLeft() - getPaddingRight();
    int childHeightSize = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();

    //1.測(cè)量Decor
    int size = getChildCount();
    for (int i = 0; i < size; ++i) {
      final View child = getChildAt(i);
      if (child.getVisibility() != GONE) {
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        if (lp != null && lp.isDecor) {//僅對(duì)Decor進(jìn)行測(cè)量
          //省略若干代碼,主要負(fù)責(zé)對(duì)Decor控件的測(cè)量
          ...
        }
      }
    }

    mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY);
    mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY);

    // 2.從Adapter中獲取childView
    mInLayout = true;
    populate();
    mInLayout = false;

    // 3.測(cè)量非Decor的childView
    size = getChildCount();
    for (int i = 0; i < size; ++i) {
      final View child = getChildAt(i);
      if (child.getVisibility() != GONE) {
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        if (lp == null || !lp.isDecor) {
          final int widthSpec = MeasureSpec.makeMeasureSpec(
              (int) (childWidthSize * lp.widthFactor), MeasureSpec.EXACTLY);
          child.measure(widthSpec, mChildHeightMeasureSpec);
        }
      }
    }
  }

簡(jiǎn)單總結(jié)就是三件事情。

2.1 測(cè)量Decor控件

可能很多人有些懵x了,Decor是個(gè)啥?
其實(shí)Decor是一個(gè)接口,在ViewPager內(nèi)部定義的,并且該接口是沒有定義任何內(nèi)容的。唯一的作用就是如果你的控件實(shí)現(xiàn)了Decor接口,那么你的控件就屬于DecorView了。
我們知道ViewPager的數(shù)據(jù)是通過Adapter管理的,但其實(shí)還有一種方式給ViewPager添加childView.

#layout.xml
<ViewPager>
  <DecorView />
</ViewPager>

上面這種直接在ViewPager布局內(nèi)部添加控件也是可以的,但是要求DecorView必須實(shí)現(xiàn)Decor接口,否則將不予顯示。
在ViewPager的addView方法中會(huì)對(duì)childView進(jìn)行判斷,也看一下代碼吧!

 @Override
  public void addView(View child, int index, ViewGroup.LayoutParams params) {
    if (!checkLayoutParams(params)) {
      params = generateLayoutParams(params);
    }
    final LayoutParams lp = (LayoutParams) params;
    lp.isDecor |= child instanceof Decor; //在此處給isDecor賦值

    //省略無關(guān)代碼
    ...
  }

至于addView()方法是如何調(diào)用,可以參考本人博客 ViewGroup如何加載布局中的View?
而上面的代碼我們要注意的是lp.isDecor,這是ViewPager為它的childView準(zhǔn)備的LayoutParams,在onMeasure的第一步中就是根據(jù)lp.isDecor來挑選出Decor控件來測(cè)量的。
至于Decor的測(cè)量過程與本文主題無關(guān),在此就不詳述了,有興趣的可以自己去查看源碼。

2.2 從Adapter中創(chuàng)建ChildView(populate方法)

ViewPager也是采用Observable模式來設(shè)計(jì)的,數(shù)據(jù)通過PagerAdapter來管理,并且childView也是通過PagerAdapter來創(chuàng)建的,ViewPager主要負(fù)責(zé)界面交互相關(guān)的工作。
對(duì)PagerAdapter并不會(huì)做太詳細(xì)的介紹,直接給一個(gè)示例代碼吧。

public class AutoScrollAdapter extends PagerAdapter {

  //省略構(gòu)造方法代碼
  ...

  @Override
  public void destroyItem(ViewGroup container, int position, Object object) {

  }

  @Override
  public int getCount() {
    return mData.size();
  }

  @Override
  public boolean isViewFromObject(View view, Object object) {
    return view == object;
  }

  @Override
  public Object instantiateItem(ViewGroup container, int position) {
    View itemView = new TextView(mContext); //通過各種方法新建一個(gè)childView
    container.addView(itemView);//將childView添加到ViewPager中
    return itemView;
  }
}

這四個(gè)方法是必須要重寫的,方法的含義根據(jù)方法名就能看出來。這里主要要講一下最后這個(gè)方法instantiateItem()。它負(fù)責(zé)向ViewPager提供childView,這里調(diào)用的addView方法是被ViewPager重寫過的,所以會(huì)對(duì)lp.isDecor賦值,并且我們可以知道,這里的isDecor=false。

有些人可能要問,這一步的主角不應(yīng)該是populate()方法嗎?的確應(yīng)該是populate方法,但是由于這個(gè)方法比較復(fù)雜,為了閱讀的連貫性考慮,博主決定單獨(dú)提出來,一會(huì)兒再講它。
在這里主要告訴大家,populate()方法內(nèi)部會(huì)調(diào)用Adapter.instantiateItem()方法,也就是將Adapter中的childView添加到ViewPager中來,為下一步做準(zhǔn)備。

2.3 測(cè)量ChildView

有了上面的分析,這一步的內(nèi)容就很好理解了。
簡(jiǎn)單來說就是,遍歷所有的childView,挑選出lp.isDecor==false的childView,然后調(diào)用view.measure()方法讓childView自己去完成測(cè)量。
還有一點(diǎn)需要注意,就是childView的寬度 width= childWidthSize * lp.widthFactor。
childWidthSize就是ViewPager的寬度,lp.widthFactor代表這個(gè)childView占幾個(gè)頁面。
lp.widthFactor默認(rèn)情況下是1.0,可以重寫PagerAdapter.getPageWidth(pos)方法來修改這個(gè)值。
到此,ViewPager的測(cè)量過程就完成了。

3.populate()方法

可以說這是ViewPager最核心的一個(gè)方法,所以單獨(dú)作為一個(gè)小節(jié)來分析。
在分析源碼之前,必須先介紹一個(gè)類——ItemInfo

3.1 ItemInfo是什么?

static class ItemInfo {
    Object object; //childView
    int position;  //childView在Adapter中的位置
    boolean scrolling; //是否在滾動(dòng)
    float widthFactor; //寬度的倍數(shù),默認(rèn)情況下是1
    float offset;    //頁面的偏移參數(shù),粗暴的理解就是第幾個(gè)頁面
  }

這是ViewPager內(nèi)部定義的一個(gè)靜態(tài)類,將childView相關(guān)的屬性進(jìn)行了包裝,主要是為了方便對(duì)childView的管理。
并且在ViewPager內(nèi)部還維護(hù)了一個(gè)ArrayList,由ItemInfo對(duì)象組成,屬性名是mItems。
這個(gè)list的長(zhǎng)度就是由mOffscreenPageLimit來決定的,這個(gè)在后面的代碼分析中會(huì)看到。
好了,了解了基本對(duì)象之后,就可以開始分析populate方法了。
注意:由于代碼比較長(zhǎng),為了方便閱讀博主打算將populate()方法的代碼分段講解,如過代碼中沒有方法聲明,則表示該段代碼屬于populate()方法。

3.2 獲取當(dāng)前的ItemInfo對(duì)象

從這里開始,對(duì)populate()方法的源碼進(jìn)行分析,分析內(nèi)容主要在代碼的注釋中編寫。

  void populate(int newCurrentItem) {
    ItemInfo oldCurInfo = null;
    int focusDirection = View.FOCUS_FORWARD;
    if (mCurItem != newCurrentItem) {
      focusDirection = mCurItem < newCurrentItem ? View.FOCUS_RIGHT : View.FOCUS_LEFT;
      oldCurInfo = infoForPosition(mCurItem); //獲取舊的ItemInfo對(duì)象
      mCurItem = newCurrentItem;  //更新mCurItem的值,就是在Adapter中的position
    }
    //省略無關(guān)代碼
    ...
    //mOffscreenPageLimit就是setOffscreenPageLimit方法設(shè)置的值
    final int pageLimit = mOffscreenPageLimit;

    //根據(jù)下面三行代碼可知:mItems的長(zhǎng)度就是 2 * pageLimit + 1
    //這里聲明的startPos和endPos在后面會(huì)起作用,大家注意一下
    final int startPos = Math.max(0, mCurItem - pageLimit);
    final int N = mAdapter.getCount();
    final int endPos = Math.min(N-1, mCurItem + pageLimit);

    // 遍歷mItems列表,找出mCurItem對(duì)應(yīng)的ItemInfo對(duì)象,是根據(jù)position來判斷的
    int curIndex = -1;
    ItemInfo curItem = null;
    for (curIndex = 0; curIndex < mItems.size(); curIndex++) {
      final ItemInfo ii = mItems.get(curIndex);
      if (ii.position >= mCurItem) {
        if (ii.position == mCurItem) curItem = ii;
        break;
      }
    }
    // 如果mItems中還未保存該ItemInfo,則創(chuàng)建一個(gè)IntemInfo對(duì)象
    if (curItem == null && N > 0) {
      curItem = addNewItem(mCurItem, curIndex);
    }
    ...

這里要注意的一點(diǎn)是,在新建ItemInfo對(duì)象時(shí),我們是調(diào)用的addNewItem方法,它的代碼如下所示。

ItemInfo addNewItem(int position, int index) {
    ItemInfo ii = new ItemInfo(); //新建一個(gè)ItemInfo對(duì)象
    ii.position = position;
    ii.object = mAdapter.instantiateItem(this, position);//用Adapter創(chuàng)建一個(gè)childView
    ii.widthFactor = mAdapter.getPageWidth(position);//默認(rèn)返回1.0f
    if (index < 0 || index >= mItems.size()) { //添加到mItems中
      mItems.add(ii);
    } else {
      mItems.add(index, ii);
    }
    return ii;
  }

不管是從mItems中提取還是新建一個(gè)ItemInfo對(duì)象,總之我們已經(jīng)得到了curItem,即當(dāng)前的IntemInfo對(duì)象。

3.3 管理mItems中的其余對(duì)象

因?yàn)槲覀兊膍Items長(zhǎng)度是有限的,并且與pageLimit有關(guān),所以很可能出現(xiàn)頁面總數(shù)大于mItems長(zhǎng)度的情況。當(dāng)顯示的頁面改變時(shí),我們必須將一些ItemInfo添加進(jìn)來,將另一些ItemInfo移除。
以保證我們的mItems中的ItemInfo.position是這樣的:
[ startPos … mCurItem … endPos ]

其中:
mCurItem = curItem.position
startPos = mCurItem - pagLimit
endPos = mCurItem + pagLimit

具體如何操作,我們來看代碼

    if (curItem != null) {
      //1.調(diào)整curItem左邊的對(duì)象
      float extraWidthLeft = 0.f;

      // curIndex是curItem在mItems中的索引
      // itemIndex就是curItem左邊的ItemInfo的索引
      int itemIndex = curIndex - 1; 
      //獲取左邊的ItemInfo對(duì)象
      ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
      final int clientWidth = getClientWidth();
      //curItem左邊需要的寬度,默認(rèn)情況下為1.0f
      final float leftWidthNeeded = clientWidth <= 0 ? 0 :
          2.f - curItem.widthFactor + (float) getPaddingLeft() / (float) clientWidth;
      //遍歷mItems左半部分,即curIndex左邊的對(duì)象
      //只有在pos < startPos時(shí)才能退出循環(huán),否則會(huì)一直遍歷到pos=0
      for (int pos = mCurItem - 1; pos >= 0; pos--) {
        // 建議大家先從下面的else if開始看,因?yàn)檫@里的邏輯是準(zhǔn)備退出循環(huán)了
        if (extraWidthLeft >= leftWidthNeeded && pos < startPos) {
          //當(dāng)pos < startPos,說明mItems左邊部分已經(jīng)調(diào)整完畢了
          //此時(shí)的ii代表的是,startPos左邊的對(duì)象了
          if (ii == null) { 
            break;
          }
          //如果startPos左邊還有對(duì)象,需要從mItems中移除
          if (pos == ii.position && !ii.scrolling) {
            mItems.remove(itemIndex);
            mAdapter.destroyItem(this, pos, ii.object);
            itemIndex--;
            curIndex--;
            ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
          }

        //如果curIndex左邊的ItemInfo對(duì)象不為null
        } else if (ii != null && pos == ii.position) {
          extraWidthLeft += ii.widthFactor; //累加curItem左邊需要的寬度
          itemIndex--;           //再往curIndex左邊移一個(gè)位置
          ii = itemIndex >= 0 ? mItems.get(itemIndex) : null; //取出ItemInfo對(duì)象

        //如果curIndex左邊的ItemInfo為null
        } else {
          //新建一個(gè)ItemInfo對(duì)象,添加到itemIndex的右邊
          ii = addNewItem(pos, itemIndex + 1); 
          extraWidthLeft += ii.widthFactor;  //累加左邊寬度
          curIndex++;  //由于往mItems中插入了一個(gè)對(duì)象,故curIndex需要加1
          ii = itemIndex >= 0 ? mItems.get(itemIndex) : null; //去除ItemInfo
        }
      }

      //2.調(diào)整curItem右邊的對(duì)象,邏輯與上面類似
      //代碼省略
      ...
      // 3.計(jì)算mItems中的偏移參數(shù)
      calculatePageOffsets(curItem, curIndex, oldCurInfo);
    }

代碼主要是一些邏輯,需要大家靜下心來讀,也不知道講清除了沒有。(發(fā)現(xiàn)要把代碼翻譯成文字真是累,一句代碼要用一大段文字來說明)
對(duì)于calculatePageOffsets方法,就不貼源碼分析了,主要說一下它做了哪些事情吧

根據(jù)oldItem.position與curItem.position的大小關(guān)系,來確定curItem的offset值
再分別對(duì)curItem的左邊和右邊的Item寫入offset值
mPageMargin是頁面之間的間隔, marginOffset = mPageMargin / childWidth
每個(gè)頁面的offset = mAdapter.getPageWidth(pos) + marginOffset
參照上面的四點(diǎn)提示,大家去讀源碼應(yīng)該也沒啥難度的,關(guān)鍵是都是一些邏輯處理很難文字化說明。

3.4 一些收尾工作

    // 將ItemInfo的內(nèi)容更新到childView的LayoutParams中
    final int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
      final View child = getChildAt(i);
      final LayoutParams lp = (LayoutParams) child.getLayoutParams();
      lp.childIndex = i;
      if (!lp.isDecor && lp.widthFactor == 0.f) {
        final ItemInfo ii = infoForChild(child);
        if (ii != null) {
          lp.widthFactor = ii.widthFactor;
          lp.position = ii.position;
        }
      }
    }
    //根據(jù)lp.position的大小對(duì)所有childView進(jìn)行排序,另外DecorView是排在其他child之前的
    sortChildDrawingOrder();

OK,populate方法分析到此就結(jié)束了。

4. onLayout

布局也是先布局Decor,再布局Adapter創(chuàng)建的childView,直接上源碼吧。

  @Override
  protected void onLayout(boolean changed, int l, int t, int r, int b) {
    final int count = getChildCount();
    int width = r - l;
    int height = b - t;

    //1.布局Decor,根據(jù)lp.isDecor來篩選DecorView
    //代碼略
    ...

    final int childWidth = width - paddingLeft - paddingRight;

    for (int i = 0; i < count; i++) {
      final View child = getChildAt(i);
      if (child.getVisibility() != GONE) {
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        ItemInfo ii;
        //此處將DecorView過濾掉,并且根據(jù)view從mItems中查找ItemInfo對(duì)象
        //如果ViewPager布局中添加了未實(shí)現(xiàn)Decor接口的控件,將不會(huì)被布局
        //因?yàn)闊o法從mItems中查找到ItemInfo對(duì)象
        if (!lp.isDecor && (ii = infoForChild(child)) != null) {
          //計(jì)算當(dāng)前page的左邊界偏移值,此處的offset會(huì)隨著頁面增加而增加
          int loff = (int) (childWidth * ii.offset);
          int childLeft = paddingLeft + loff;
          int childTop = paddingTop;
          if (lp.needsMeasure) {//如果需要重新測(cè)量,則重新測(cè)量之
            lp.needsMeasure = false;
            final int widthSpec = MeasureSpec.makeMeasureSpec(
                (int) (childWidth * lp.widthFactor),
                MeasureSpec.EXACTLY);
            final int heightSpec = MeasureSpec.makeMeasureSpec(
                (int) (height - paddingTop - paddingBottom),
                MeasureSpec.EXACTLY);
            child.measure(widthSpec, heightSpec);
          }
          //child調(diào)用自己的layout方法來布局自己
          child.layout(childLeft, childTop,
              childLeft + child.getMeasuredWidth(),
              childTop + child.getMeasuredHeight());
        }
      }
    }
    mTopPageBounds = paddingTop;
    mBottomPageBounds = height - paddingBottom;
    mDecorChildCount = decorCount;
    //如果是首次布局,則會(huì)調(diào)用scrollToItem方法
    if (mFirstLayout) {
      scrollToItem(mCurItem, false, 0, false);
    }
    mFirstLayout = false;
  }

布局這一塊的代碼相對(duì)來說要簡(jiǎn)單一些,就是根據(jù)offset偏移量來計(jì)算出left,right, top, bottom值,然后直接調(diào)用View.layout方法進(jìn)行布局。
但是,這里需要插一句,在用ViewPager實(shí)現(xiàn)輪播控件時(shí),有一種方法是將Adapter.getCount返回Integer.MAX_VALUE,已達(dá)到偽循環(huán)播放的目的。從上面的代碼可以看到,此時(shí)這個(gè)offset值會(huì)不斷的變大,那么

int loff = (int) (childWidth * ii.offset);

這個(gè)loff很可能會(huì)超出int的最大值邊界。
所以,以后大家實(shí)現(xiàn)輪播控件時(shí),還是不要采用這種方法了。

然后,回過頭來再說下scrollToItem方法
注意上面調(diào)用scrollToItem時(shí),最后一個(gè)參數(shù)傳遞的是false,而這個(gè)參數(shù)就是決定是否調(diào)用onPageSelected回調(diào)函數(shù)的。
看代碼:

  private void scrollToItem(int item, boolean smoothScroll, int velocity,
      boolean dispatchSelected) {
    final ItemInfo curInfo = infoForPosition(item);
    int destX = 0;
    if (curInfo != null) {
      final int width = getClientWidth();
      destX = (int) (width * Math.max(mFirstOffset,
          Math.min(curInfo.offset, mLastOffset)));
    }
    if (smoothScroll) {
      smoothScrollTo(destX, 0, velocity);
      if (dispatchSelected) {
        dispatchOnPageSelected(item);
      }
    } else {
      if (dispatchSelected) { //是否需要分發(fā)OnPageSelected回調(diào)
        dispatchOnPageSelected(item);
      }
      completeScroll(false);
      scrollTo(destX, 0);
      pageScrolled(destX);
    }
  }

也就是說,第一次布局ViewPager時(shí)雖然會(huì)顯示一個(gè)頁面,卻不會(huì)調(diào)用onPageSelected方法。

onLayout的分析也到此結(jié)束了,至于onDraw方法ViewPager并沒有做什么,只是編寫了繪制Page之間間隔的代碼,就不做分析了。

當(dāng)然,ViewPager的代碼還不止這些,此文分析的僅僅是它的骨架,還有許多其他處理如onInterceptTouchEvent方法,pageScrolled方法等等,這些就留給讀者自己去分析吧。

理解了這篇文章之后,對(duì)ViewPager的工作原理也有一定程度的了解了,相信再去讀那些代碼難度不會(huì)很大。
至于篇頭提到的三個(gè)問題,相信各位也已經(jīng)有了答案。

相關(guān)文章

  • 快速關(guān)閉android studio的自動(dòng)保存功能教程

    快速關(guān)閉android studio的自動(dòng)保存功能教程

    這篇文章主要介紹了快速關(guān)閉android studio的自動(dòng)保存功能教程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-04-04
  • loadavg數(shù)據(jù)異常引發(fā)問題起源分析

    loadavg數(shù)據(jù)異常引發(fā)問題起源分析

    這篇文章主要為大家介紹了loadavg數(shù)據(jù)異常引發(fā)問題起源分析詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-11-11
  • kotlin實(shí)現(xiàn)語音聊天機(jī)器人案例詳解

    kotlin實(shí)現(xiàn)語音聊天機(jī)器人案例詳解

    Android智能問答機(jī)器人是時(shí)下非常流行的一種服務(wù),微軟“小冰”的出現(xiàn)更是讓其實(shí)實(shí)在在的風(fēng)靡了一把。那么,本文章就將帶領(lǐng)大家完整的實(shí)現(xiàn)整個(gè)問答機(jī)器人的制作
    2023-02-02
  • Android 開發(fā)使用PopupWindow實(shí)現(xiàn)彈出警告框的復(fù)用類示例

    Android 開發(fā)使用PopupWindow實(shí)現(xiàn)彈出警告框的復(fù)用類示例

    這篇文章主要介紹了Android 開發(fā)使用PopupWindow實(shí)現(xiàn)彈出警告框的復(fù)用類,結(jié)合實(shí)例形式分析了Android基于PopupWindow彈出警告框的復(fù)用類具體布局與功能實(shí)現(xiàn)技巧,需要的朋友可以參考下
    2020-05-05
  • 如何讓安卓(Android)子控件超出父控件的范圍顯示

    如何讓安卓(Android)子控件超出父控件的范圍顯示

    下面我們來看一下關(guān)于安卓(Android)子控件超出父控件的范圍顯示出來的方法,希望這篇文章能夠幫助到各位安卓(Android)的開發(fā)者們,有碰到此問題的朋友可以進(jìn)來看看哦。
    2016-08-08
  • Android實(shí)現(xiàn)文本排版

    Android實(shí)現(xiàn)文本排版

    這篇文章主要介紹了Android實(shí)現(xiàn)文本排版,對(duì)多行文本進(jìn)行排版布局,每一行的內(nèi)容又分為兩部分,左邊為標(biāo)題,右邊為描述,左邊內(nèi)容長(zhǎng)度不確定,右邊的內(nèi)容需要對(duì)齊,需要的朋友可以參考下
    2016-04-04
  • android camera yuv幀水平翻轉(zhuǎn)實(shí)例

    android camera yuv幀水平翻轉(zhuǎn)實(shí)例

    今天小編就為大家分享一篇android camera yuv幀水平翻轉(zhuǎn)實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2018-08-08
  • Android app開發(fā)中Retrofit框架的初步上手使用

    Android app開發(fā)中Retrofit框架的初步上手使用

    這篇文章主要介紹了Android app開發(fā)中Retrofit框架的初步上手使用,Retrofit 2.0發(fā)布以來獲得了巨大的人氣增長(zhǎng),并且經(jīng)常被開發(fā)者們拿來與Volley比較,需要的朋友可以參考下
    2016-02-02
  • Android獲得內(nèi)/外置存儲(chǔ)卡路徑的方法

    Android獲得內(nèi)/外置存儲(chǔ)卡路徑的方法

    我們知道Android上一般都有外置的存儲(chǔ)卡,內(nèi)置存儲(chǔ)卡路徑大家都知道怎么獲得的。那么如何獲取外置存儲(chǔ)卡的位置呢?下面小編通過本文給大家分享下
    2017-01-01
  • 安卓逆向半次元app逆向分析源碼

    安卓逆向半次元app逆向分析源碼

    這篇文章主要為大家介紹了安卓逆向半次元app逆向分析的源碼示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-02-02

最新評(píng)論

比如县| 揭东县| 措美县| 丽江市| 谢通门县| 桃源县| 景泰县| 惠安县| 巫溪县| 乌鲁木齐县| 蚌埠市| 江油市| 丁青县| 周至县| 和静县| 固原市| 普兰店市| 阿拉善右旗| 诸暨市| 广德县| 海阳市| 清丰县| 伊金霍洛旗| 芮城县| 龙江县| 乐清市| 文山县| 响水县| 堆龙德庆县| 雷波县| 和林格尔县| 昆山市| 营山县| 丰原市| 克山县| 临沭县| 贺州市| 北票市| 中牟县| 祁东县| 景宁|