Android性能優(yōu)化之ViewPagers?+?Fragment緩存優(yōu)化
前言
大家看標(biāo)題,可能會有點(diǎn)兒懵,什么是ViewPagers,因?yàn)樵诤芫弥埃覀兪褂玫亩际荲iewPager,但是現(xiàn)在更多的是在用ViewPager2,因此用ViewPagers(ViewPager、ViewPager2)來代替兩者,主要介紹兩者的區(qū)別。
ViewPagers嵌套Fragment架構(gòu),在我們常用的App中隨處可見,抖音的首頁、各大電商app首頁(淘寶、京東、拼多多)等,通過左右滑動切換Tab;但因?yàn)閂iewPager的預(yù)加載機(jī)制存在,
我們先看下ViewPager的源碼:
public void setOffscreenPageLimit(int limit) {
if (limit < DEFAULT_OFFSCREEN_PAGES) {
Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to "
+ DEFAULT_OFFSCREEN_PAGES);
limit = DEFAULT_OFFSCREEN_PAGES;
}
if (limit != mOffscreenPageLimit) {
mOffscreenPageLimit = limit;
populate();
}
}當(dāng)我們設(shè)置offscreenPageLimit(離屏加載)的數(shù)值時,我們可以看到limit的值是有限制,不能小于DEFAULT_OFFSCREEN_PAGES
private static final int DEFAULT_OFFSCREEN_PAGES = 1;
那么就意味著ViewPager默認(rèn)支持預(yù)加載,我們看下面這張圖

如果紅色區(qū)域默認(rèn)為首頁,根據(jù)ViewPager默認(rèn)預(yù)加載的閾值,那么左右兩邊的頁面同樣也會被加載,如果有網(wǎng)絡(luò)請求,也就是說,我們沒有打開左邊的頁面,它已經(jīng)默認(rèn)進(jìn)行了網(wǎng)絡(luò)請求,這種體驗(yàn)是非常差的,因?yàn)闀诎档乩锵牧髁俊?/p>
理想情況下,我們需要的是打開某個頁面的時候才去加載,這里就需要通過懶加載的方式優(yōu)化。
1 ViewPager懶加載優(yōu)化
1.1 ViewPager的緩存機(jī)制
很多時候,我們在使用Fragment的時候,發(fā)現(xiàn)打開過的頁面再回來,頁面沒有重建刷新,很多人覺得是Fragment是有緩存的,其實(shí)并不是Fragment有緩存,而是ViewPager具備緩存能力;
如果有小伙伴使用過單Activity + 多Fragment架構(gòu)的時候就會發(fā)現(xiàn),打開過的頁面再次返回的時候,F(xiàn)ragment會被重建,所以兩種架構(gòu)都有利弊,關(guān)鍵看我們怎么選擇,下面我們看下ViewPager的緩存機(jī)制。
public void setAdapter(@Nullable PagerAdapter adapter) {
if (mAdapter != null) {
①
mAdapter.setViewPagerObserver(null);
mAdapter.startUpdate(this);
for (int i = 0; i < mItems.size(); i++) {
final ItemInfo ii = mItems.get(i);
mAdapter.destroyItem(this, ii.position, ii.object);
}
mAdapter.finishUpdate(this);
mItems.clear();
removeNonDecorViews();
mCurItem = 0;
scrollTo(0, 0);
}
②
final PagerAdapter oldAdapter = mAdapter;
mAdapter = adapter;
mExpectedAdapterCount = 0;
③
if (mAdapter != null) {
if (mObserver == null) {
mObserver = new PagerObserver();
}
mAdapter.setViewPagerObserver(mObserver);
mPopulatePending = false;
final boolean wasFirstLayout = mFirstLayout;
mFirstLayout = true;
mExpectedAdapterCount = mAdapter.getCount();
if (mRestoredCurItem >= 0) {
mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);
setCurrentItemInternal(mRestoredCurItem, false, true);
mRestoredCurItem = -1;
mRestoredAdapterState = null;
mRestoredClassLoader = null;
} else if (!wasFirstLayout) {
④
populate();
} else {
⑤
requestLayout();
}
}
// Dispatch the change to any listeners
if (mAdapterChangeListeners != null && !mAdapterChangeListeners.isEmpty()) {
for (int i = 0, count = mAdapterChangeListeners.size(); i < count; i++) {
mAdapterChangeListeners.get(i).onAdapterChanged(this, oldAdapter, adapter);
}
}
}核心方法就是setAdapter,像RecyclerView一樣,因?yàn)闀芯彺?,所以?dāng)頁面滑動的時候,如果緩存中存在頁面,那么就會從緩存中取,如果沒有,就需要去創(chuàng)建新的頁面,所以我們先來關(guān)注一下PagerAdapter
public abstract class PagerAdapter {
private final DataSetObservable mObservable = new DataSetObservable();
private DataSetObserver mViewPagerObserver;
public static final int POSITION_UNCHANGED = -1;
public static final int POSITION_NONE = -2;
public abstract int getCount();
//開始更新
public void startUpdate(@NonNull ViewGroup container) {
startUpdate((View) container);
}
//初始化頁面
@NonNull
public Object instantiateItem(@NonNull ViewGroup container, int position) {
return instantiateItem((View) container, position);
}
//銷毀頁面
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
destroyItem((View) container, position, object);
}
//結(jié)束刷新
public void finishUpdate(@NonNull ViewGroup container) {
finishUpdate((View) container);
}
}PagerAdapter是一個抽象類,那么這些方法肯定是具體實(shí)現(xiàn)類實(shí)現(xiàn),如果我們在使用ViewPager嵌套Fragment的時候,使用的是FragmentPageAdapter
接著回到setAdapter方法中:
- ①:有一個全局變量 mAdapter,如果是第一個加載進(jìn)來,那么mAdapter是空的,走到②
- ②:這里就是將我們傳入的adapter給mAdapter賦值
- ③:這個時候mAdapter不為空,這里需要關(guān)注幾個參數(shù):
wasFirstLayout = true mRestoredCurItem = -1
所以這里直接走到⑤,調(diào)用requestLayout方法,會執(zhí)行到onMeasure,在這個方法中,會執(zhí)行populate方法(這個大家自己去爬樓)
populate干了什么呢?代碼太多了就不貼出來了,直接上圖:

如果是默認(rèn)緩存(mOffscreenPageLimit = 1),那么在mItems就會緩存3個Fragment
private final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>();
當(dāng)頁面滑動時,page2成為了當(dāng)前頁,那么ViewPager的populate做了什么操作呢?
- (1)首先page3會被預(yù)加載,這個時候調(diào)用了PagerAdapter的instantiateItem方法新建頁面,并放在mItems集合中,并且設(shè)置為不可見的狀態(tài)(setUserVisibleHint(false)),所有緩存中不可見的頁面同理(2)page1就會從緩存中移除,調(diào)用了PagerAdapter的destroyItem方法,curPage會成為mItems中第一個緩存對象;
- (3)將page2設(shè)置為當(dāng)前展示的Fragment
因此populate干的主要工作就是,隨著頁面的滑動,將Page從緩存中移除銷毀,或者將新頁面新建加入緩存中。
1.2 ViewPager懶加載方案
如上所述,ViewPager默認(rèn)就是開啟預(yù)加載的,而且默認(rèn)最多能夠緩存3個Fragment頁面,那么為了避免流量的消耗,需要我們針對預(yù)加載這種情況進(jìn)行頁面懶加載,只有當(dāng)頁面可見的時候,才能加載數(shù)據(jù)。
class MainLazyLoadAdapter(
fragmentManager: FragmentManager,
val fragments:MutableList<Fragment>
) : FragmentPagerAdapter(fragmentManager) {
override fun getCount(): Int {
return fragments.size
}
override fun getItem(position: Int): Fragment {
return fragments[position]
}
}class LazyFragment(val index:Int) : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.e("TAG","LazyFragment $index onCreate")
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
Log.e("TAG","LazyFragment $index onCreateView")
return inflater.inflate(R.layout.fragment_lazy, container, false)
}
}val fragments = mutableListOf<Fragment>()
for (index in 0..5) {
fragments.add(LazyFragment(index))
}
vp_lazy_load.adapter = MainLazyLoadAdapter(supportFragmentManager, fragments)首先我們先看默認(rèn)預(yù)加載狀態(tài),驗(yàn)證之前源碼中的原理:
//第一次進(jìn)來 2022-08-28 13:41:15.759 12677-12677/com.lay.image_process E/TAG: LazyFragment 0 onCreate 2022-08-28 13:41:15.760 12677-12677/com.lay.image_process E/TAG: LazyFragment 0 onCreateView 2022-08-28 13:41:15.783 12677-12677/com.lay.image_process E/TAG: LazyFragment 1 onCreate 2022-08-28 13:41:15.784 12677-12677/com.lay.image_process E/TAG: LazyFragment 1 onCreateView
我們看到第一次進(jìn)來,第二個Fragment被加載進(jìn)來,然后右滑,第三個Fragment被加載
2022-08-28 13:41:15.759 12677-12677/com.lay.image_process E/TAG: LazyFragment 0 onCreate 2022-08-28 13:41:15.760 12677-12677/com.lay.image_process E/TAG: LazyFragment 0 onCreateView 2022-08-28 13:41:15.783 12677-12677/com.lay.image_process E/TAG: LazyFragment 1 onCreate 2022-08-28 13:41:15.784 12677-12677/com.lay.image_process E/TAG: LazyFragment 1 onCreateView 2022-08-28 13:48:45.248 12677-12677/com.lay.image_process E/TAG: LazyFragment 2 onCreate 2022-08-28 13:48:45.250 12677-12677/com.lay.image_process E/TAG: LazyFragment 2 onCreateView
當(dāng)我們滑到第4個Fragment的時候,左滑回到第3個Fragment,發(fā)現(xiàn)并沒有重建是因?yàn)榫彺娴脑?,因?yàn)榛降?個Fragment的時候,第2個Fragment已經(jīng)被銷毀了,再回到第3個Fragment的時候,第2個Fragment被重建,走了onCreateView方法
2022-08-28 13:41:15.759 12677-12677/com.lay.image_process E/TAG: LazyFragment 0 onCreate 2022-08-28 13:41:15.760 12677-12677/com.lay.image_process E/TAG: LazyFragment 0 onCreateView 2022-08-28 13:41:15.783 12677-12677/com.lay.image_process E/TAG: LazyFragment 1 onCreate 2022-08-28 13:41:15.784 12677-12677/com.lay.image_process E/TAG: LazyFragment 1 onCreateView 2022-08-28 13:48:45.248 12677-12677/com.lay.image_process E/TAG: LazyFragment 2 onCreate 2022-08-28 13:48:45.250 12677-12677/com.lay.image_process E/TAG: LazyFragment 2 onCreateView 2022-08-28 13:50:00.439 12677-12677/com.lay.image_process E/TAG: LazyFragment 3 onCreate 2022-08-28 13:50:00.440 12677-12677/com.lay.image_process E/TAG: LazyFragment 3 onCreateView 2022-08-28 13:50:01.344 12677-12677/com.lay.image_process E/TAG: LazyFragment 4 onCreate 2022-08-28 13:50:01.345 12677-12677/com.lay.image_process E/TAG: LazyFragment 4 onCreateView 2022-08-28 13:50:03.315 12677-12677/com.lay.image_process E/TAG: LazyFragment 1 onCreateView
首先我們先看下,Adapter重建Fragment的時候的核心代碼
public Object instantiateItem(@NonNull ViewGroup container, int position) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
final long itemId = getItemId(position);
// Do we already have this fragment?
String name = makeFragmentName(container.getId(), itemId);
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
mCurTransaction.attach(fragment);
} else {
fragment = getItem(position);
if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), itemId));
}
if (fragment != mCurrentPrimaryItem) {
fragment.setMenuVisibility(false);
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED);
} else {
//關(guān)鍵代碼
fragment.setUserVisibleHint(false);
}
}
return fragment;
}我們可以看到,當(dāng)前Fragment如果被創(chuàng)建但是沒有在當(dāng)前頁面展示的時候,調(diào)用了fragment.setUserVisibleHint(false),也就是說setUserVisibleHint能夠監(jiān)聽當(dāng)前Fragment是否可見
所以我們對Fragment進(jìn)行改造:
class LazyFragment(val index:Int) : Fragment() {
//判斷當(dāng)前頁面是否可見
private var isShow = false
//判斷頁面是否創(chuàng)建成功
private var isViewCreated = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.e("TAG","LazyFragment $index onCreate")
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
Log.e("TAG","LazyFragment $index onCreateView")
return inflater.inflate(R.layout.fragment_lazy, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
isViewCreated = true
lazyLoad()
}
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
super.setUserVisibleHint(isVisibleToUser)
Log.e("TAG","LazyFragment $index isVisibleToUser $isVisibleToUser")
if(isVisibleToUser){
isShow = true
//才有資格去懶加載
lazyLoad()
}else{
isShow = false
}
}
private fun lazyLoad() {
if(isViewCreated && isShow){
Log.e("TAG","Fragment $index loadData")
}
}
override fun onDestroy() {
super.onDestroy()
isViewCreated = false
isShow = false
}
}如果按照之前的方式,當(dāng)調(diào)用onViewCreated方法的時候,我們就會加載數(shù)據(jù);做了懶加載處理之后,重寫了setUserVisibleHint方法,當(dāng)前頁面可見的時候,才有資格去加載數(shù)據(jù),這樣即便創(chuàng)建了Fragment,但是如果不可見就不會加載數(shù)據(jù)
2022-08-28 14:06:29.776 25904-25904/com.lay.image_process E/TAG: LazyFragment 0 isVisibleToUser false 2022-08-28 14:06:29.776 25904-25904/com.lay.image_process E/TAG: LazyFragment 1 isVisibleToUser false 2022-08-28 14:06:29.776 25904-25904/com.lay.image_process E/TAG: LazyFragment 0 isVisibleToUser true 2022-08-28 14:06:29.782 25904-25904/com.lay.image_process E/TAG: LazyFragment 0 onCreate 2022-08-28 14:06:29.783 25904-25904/com.lay.image_process E/TAG: LazyFragment 0 onCreateView 2022-08-28 14:06:29.796 25904-25904/com.lay.image_process E/TAG: Fragment 0 loadData 2022-08-28 14:06:29.805 25904-25904/com.lay.image_process E/TAG: LazyFragment 1 onCreate 2022-08-28 14:06:29.805 25904-25904/com.lay.image_process E/TAG: LazyFragment 1 onCreateView 2022-08-28 14:06:59.395 25904-25904/com.lay.image_process E/TAG: LazyFragment 2 isVisibleToUser false 2022-08-28 14:06:59.396 25904-25904/com.lay.image_process E/TAG: LazyFragment 0 isVisibleToUser false 2022-08-28 14:06:59.396 25904-25904/com.lay.image_process E/TAG: LazyFragment 1 isVisibleToUser true 2022-08-28 14:06:59.396 25904-25904/com.lay.image_process E/TAG: Fragment 1 loadData 2022-08-28 14:06:59.399 25904-25904/com.lay.image_process E/TAG: LazyFragment 2 onCreate 2022-08-28 14:06:59.400 25904-25904/com.lay.image_process E/TAG: LazyFragment 2 onCreateView
通過日志我們可以看到,當(dāng)首次進(jìn)入的時候,雖然Fragment 1 被創(chuàng)建了,但是并沒有加載數(shù)據(jù)。
這里有個問題,既然可見之后就能加載數(shù)據(jù),那么我只在setUserVisibleHint的時候,判斷是否可見來去加載數(shù)據(jù)?
其實(shí)是不可以的,通過日志我們能夠發(fā)現(xiàn),setUserVisibleHint是早于onCreate方法調(diào)用的,也就是說在頁面還沒有創(chuàng)建時,去加載數(shù)據(jù)有可能導(dǎo)致頁面元素找不到發(fā)生空指針異常。
2 ViewPager2與ViewPager的區(qū)別
上一小節(jié),我們介紹了ViewPager的加載機(jī)制和緩存機(jī)制,那么我們把整套頁面搬過來,唯一發(fā)生變化的就是將ViewPager轉(zhuǎn)換為ViewPager2
class MainLazyLoadAdapter2(
activity: FragmentActivity,
val fragments: MutableList<Fragment>
) : FragmentStateAdapter(activity) {
override fun getItemCount(): Int {
return fragments.size
}
override fun createFragment(position: Int): Fragment {
return fragments[position]
}
}ViewPager2的適配器使用的是FragmentStateAdapter,因?yàn)镕ragmentStateAdapter繼承了RecyclerView.Adapter,因此支持了橫向滑動和豎向滑動
val fragments = mutableListOf<Fragment>()
for (index in 0..5) {
fragments.add(LazyFragment(index))
}
vp_lazy_load = findViewById(R.id.vp_lazy_load)
vp_lazy_load.adapter = MainLazyLoadAdapter2(this, fragments)用同樣的方式設(shè)置了適配器,我們看下日志輸出,就會發(fā)現(xiàn),咦?怎么跟ViewPager不一樣了
2022-08-28 14:47:11.790 15514-15514/com.lay.image_process E/TAG: LazyFragment 0 onCreate 2022-08-28 14:47:11.792 15514-15514/com.lay.image_process E/TAG: LazyFragment 0 onCreateView
剛進(jìn)來的時候,只有Fragment 1 加載了頁面,并沒有新建緩存頁面,當(dāng)我滑動到下一頁的時候,也只有下一頁的頁面進(jìn)行了重建
2022-08-28 14:47:11.790 15514-15514/com.lay.image_process E/TAG: LazyFragment 0 onCreate 2022-08-28 14:47:11.792 15514-15514/com.lay.image_process E/TAG: LazyFragment 0 onCreateView 2022-08-28 14:47:13.948 15514-15514/com.lay.image_process E/TAG: LazyFragment 1 onCreate 2022-08-28 14:47:13.948 15514-15514/com.lay.image_process E/TAG: LazyFragment 1 onCreateView
ViewPager2沒有預(yù)加載機(jī)制嗎?這里我們就需要看源碼了,直接奔向setOffscreenPageLimit方法,我們看到跟ViewPager的setOffscreenPageLimit方法是不一樣的
public void setOffscreenPageLimit(@OffscreenPageLimit int limit) {
if (limit < 1 && limit != OFFSCREEN_PAGE_LIMIT_DEFAULT) {
throw new IllegalArgumentException(
"Offscreen page limit must be OFFSCREEN_PAGE_LIMIT_DEFAULT or a number > 0");
}
mOffscreenPageLimit = limit;
// Trigger layout so prefetch happens through getExtraLayoutSize()
mRecyclerView.requestLayout();
}public static final int OFFSCREEN_PAGE_LIMIT_DEFAULT = -1;
這里的判斷條件 limit < 1 && limit != OFFSCREEN_PAGE_LIMIT_DEFAULT,有一個數(shù)值能夠通過,就是-1,這就意味著,ViewPager2默認(rèn)是不支持預(yù)加載的
但是ViewPager2的緩存策略還是存在,因?yàn)槔^承了RecyclerView的Adapter,所以緩存復(fù)用機(jī)制是跟RecyclerView一致的,默認(rèn)mViewCaches緩存池的大小是3
2022-08-28 15:30:00.579 15514-15514/com.lay.image_process E/TAG: LazyFragment 0 onCreate 2022-08-28 15:30:00.579 15514-15514/com.lay.image_process E/TAG: LazyFragment 0 onCreateView 2022-08-28 15:30:03.883 15514-15514/com.lay.image_process E/TAG: LazyFragment 1 onCreate 2022-08-28 15:30:03.884 15514-15514/com.lay.image_process E/TAG: LazyFragment 1 onCreateView 2022-08-28 15:30:05.064 15514-15514/com.lay.image_process E/TAG: LazyFragment 2 onCreate 2022-08-28 15:30:05.064 15514-15514/com.lay.image_process E/TAG: LazyFragment 2 onCreateView 2022-08-28 15:30:08.997 15514-15514/com.lay.image_process E/TAG: LazyFragment 3 onCreate 2022-08-28 15:30:08.997 15514-15514/com.lay.image_process E/TAG: LazyFragment 3 onCreateView 2022-08-28 15:30:20.005 15514-15514/com.lay.image_process E/TAG: LazyFragment 0 onCreate 2022-08-28 15:30:20.005 15514-15514/com.lay.image_process E/TAG: LazyFragment 0 onCreateView
當(dāng)我們滑動到第4個Fragment的時候,注意這里跟ViewPager不一樣的是,ViewPager的緩存是緩存當(dāng)前頁的左右兩邊,但是ViewPager2就是RecyclerView的緩存機(jī)制,順序緩存;
當(dāng)滑動到第4個Fragment的時候,因?yàn)榫彺娉卮笮?,因此LazyFragment 0 就會從緩存池中移除,當(dāng)再次滑動到LazyFragment 0的時候,就會重建!
所以當(dāng)我們還在思考如何針對ViewPager的預(yù)加載機(jī)制做懶加載操作時,請將項(xiàng)目中的ViewPager遷移至ViewPager2
附錄:
當(dāng)你的項(xiàng)目中還在使用ViewPager時,建議使用當(dāng)前這個懶加載框架
abstract class BaseLazyFragment<VM : ViewModel, VB : ViewBinding> : Fragment() {
private lateinit var viewModel: VM
private lateinit var binding: VB
private var isShow = false
private var isViewCreated = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = getViewModelInstance()
binding = getLayoutInflate(layoutInflater)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initView()
isViewCreated = true
lazyLoad()
}
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
super.setUserVisibleHint(isVisibleToUser)
if (isVisibleToUser) {
isShow = true
lazyLoad()
} else {
isShow = false
}
}
override fun onDestroy() {
super.onDestroy()
isShow = false
isViewCreated = false
}
private fun lazyLoad() {
if (isShow && isViewCreated) {
initData()
}
}
open fun initData() {}
open fun initView() {}
abstract fun getViewModelInstance(): VM
abstract fun getLayoutInflate(layoutInflater: LayoutInflater): VB
}使用方式:
class LazyFragment(val index:Int) : BaseLazyFragment<MainVM,FragmentLazy2Binding>() {
override fun initData() {
super.initData()
Log.e("TAG","LazyFragment $index initData -- ")
}
override fun getViewModelInstance(): MainVM {
return MainVM()
}
override fun getLayoutInflate(layoutInflater: LayoutInflater): FragmentLazy2Binding {
return FragmentLazy2Binding.inflate(layoutInflater)
}
}到此這篇關(guān)于Android性能優(yōu)化之ViewPagers + Fragment緩存優(yōu)化的文章就介紹到這了,更多相關(guān)Android 緩存優(yōu)化內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android中EditText setText方法的踩坑實(shí)戰(zhàn)
這篇文章主要給大家分享了一些關(guān)于Android中EditText setText方法的踩坑記錄,文中通過示例代碼介紹的非常詳細(xì),對各位Android開發(fā)者們具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-07-07
ionic監(jiān)聽android返回鍵實(shí)現(xiàn)“再按一次退出”功能
本篇文章主要介紹了ionic監(jiān)聽android返回鍵實(shí)現(xiàn)“再按一次退出”功能,非常具有實(shí)用價值,需要的朋友可以參考下2018-02-02
Android?Flutter實(shí)現(xiàn)"斑馬紋"背景的示例代碼
本文將通過實(shí)現(xiàn)一個canvas繪制斑馬紋類。使用Stack布局,將斑馬紋放在下方作為背景板,需要展示的內(nèi)容在上方。從而實(shí)現(xiàn)?“斑馬紋”背景,感興趣的可以了解一下2022-06-06
Android通過AIDL在兩個APP之間Service通信
這篇文章主要為大家詳細(xì)介紹了Android通過AIDL在兩個APP之間Service通信,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-05-05
Android實(shí)現(xiàn)點(diǎn)擊獲取驗(yàn)證碼倒計時效果
這篇文章主要介紹了Android實(shí)現(xiàn)點(diǎn)擊獲取驗(yàn)證碼倒計時效果,這種效果大家經(jīng)常遇到,想知道如何實(shí)現(xiàn)的,請閱讀本文2016-08-08
Android之使用Android-query框架開發(fā)實(shí)戰(zhàn)(一)
這篇文章主要介紹了Android之使用Android-query框架開發(fā)實(shí)戰(zhàn)(一)的相關(guān)資料,需要的朋友可以參考下2015-10-10
Flutter實(shí)現(xiàn)倒計時秒數(shù)轉(zhuǎn)時分秒然后倒計時功能
有一個需求,需要在頁面進(jìn)行顯示倒計時,倒計時結(jié)束后,做相應(yīng)的邏輯處理,這篇文章主要介紹了Flutter實(shí)現(xiàn)倒計時功能,秒數(shù)轉(zhuǎn)時分秒,然后倒計時,需要的朋友可以參考下2023-08-08
Android實(shí)現(xiàn)底部圖片選擇Dialog
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)底部圖片選擇Dialog,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-10-10
利用源碼編譯Android系統(tǒng)的APK和可執(zhí)行命令的方法
這篇文章主要介紹了利用源碼編譯Android系統(tǒng)的APK和可執(zhí)行命令的方法,示例在Linux系統(tǒng)環(huán)境上進(jìn)行構(gòu)建,需要的朋友可以參考下2016-02-02
Android中FloatingActionButton實(shí)現(xiàn)懸浮按鈕實(shí)例
這篇文章主要介紹了Android中FloatingActionButton實(shí)現(xiàn)懸浮按鈕實(shí)例,具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-04-04

