編輯:關於android開發
無意間了解到沉浸式狀態欄,感覺賊拉的高大上,於是就是試著去了解一下,就有了這篇文章。下面就來了解一下啥叫沉浸式狀態欄。傳統的手機狀態欄是呈現出黑色條狀的,有的和手機主界面有很明顯的區別。這一樣就在一定程度上犧牲了視覺寬度,界面面積變小。Google從android kitkat(Android 4.4)開始,給我們開發者提供了一套能透明的系統ui樣式給狀態欄和導航欄,這樣的話就不用向以前那樣每天面對著黑乎乎的上下兩條黑欄了,還可以調成跟Activity一樣的樣式,形成一個完整的主題,和IOS7.0以上系統一樣了,沉浸式狀態欄和主界面顏色和諧一體,視覺效果更加炫酷。不過雖然聽上去好像是很高大上的沉浸式效果,實際看上去貌似就是將內容全屏化了而已嘛。其實這算是一個爭議點了。不少人糾結於沉浸式狀態欄到底是將屏幕顯示內容擴大還是僅僅是改變狀態欄、標題欄的顏色。其實我更傾向於後者。在4.4之前狀態欄一直是黑色的,在4.4中帶來了 windowTranslucentStatus 這一特性,因此可以實現給狀態欄設置顏色,視覺上的效果,感覺容器部分和狀態欄、標題欄融為一體,更加直接的說就是改變狀態欄、標題欄的顏色,當時可以根據界面顏色改變狀態欄、標題欄的顏色實現跟加完整的界面顯示,這應該是沉浸式狀態欄受追捧的原因吧。
谷歌並沒有給出沉浸式狀態欄這個概念,谷歌只說了沉浸式模式(Immersive Mode)。不過沉浸式狀態欄這個名字其實挺不錯,只能隨大眾,但是Android的環境並沒有IOS環境一樣特別統一,比如華為rom的跟小米rom的虛擬按鍵完全不一樣,並且安卓版本眾多涉及到版本兼容問題,所有Android開發者不容易。這點在沉浸式狀態欄的開發中顯得尤為重要。如果你在4.4之前的機子上顯示沉浸式狀態欄的話,經常出現一些意想不到的結果。沉浸式是APP界面圖片延伸到狀態欄, 應用本身沉浸於狀態欄,所以如果第三方的軟件沒有為狀態欄分配圖片,那麼自然就是黑色。頂端的狀態欄和下面的虛擬按鍵都隱藏,需要的時候從邊緣劃出。沉浸模式。當啟用該模式,應用程序的界面將占據整個屏幕,系統自動將隱藏系統的狀態欄和導航欄,讓應用程序內容可以在最大顯示范圍呈現,增加大屏體驗,而當需要查看通知的時候只需要從頂部向下滑動就能呼出通知欄。沉浸模式實際上有兩種: 一種叫“沉浸模式”,狀態欄和虛擬按鈕會自動隱藏、應用自動全屏,這種模式下,應用占據屏幕的全部空間, 只有當用戶從屏幕的上方邊沿處向下劃動時, 才會退出沉浸模式, 用戶觸摸屏幕其它部分時, 不會退出該模式, 這種模式比較適用於閱讀器、 雜志類應用。另外一種叫“黏性沉浸模式”,讓狀態欄和虛擬按鈕半透明,應用使用屏幕的全部空間, 當用戶從屏幕的上方邊沿處向下滑動時,也不會退出該模式, 但是系統界面 (狀態欄、 導航欄) 將會以半透明的效果浮現在應用視圖之上 , 只有當用戶點擊系統界面上的控件時, 才會退出黏性沉浸模式。
下面來說一說具體的實現。一個Android應用程序的界面上其實是有很多系統元素的,有狀態欄、ActionBar、導航欄等。而打造沉浸式模式的用戶體驗,就是要將這些系統元素進行整合,當主界面改變時,狀態欄、ActionBar、導航欄同時也發生改變。這裡先調用getWindow().getDecorView()方法獲取到了當前界面的DecorView,然後調用它的setSystemUiVisibility()方法來設置系統UI元素的可見性。其中,SYSTEM_UI_FLAG_FULLSCREEN表示全屏的意思,也就是會將狀態欄隱藏。另外,根據Android的設計建議,ActionBar是不應該獨立於狀態欄而單獨顯示的,因此狀態欄如果隱藏了,我們同時也需要調用ActionBar的hide()方法將ActionBar也進行隱藏這種效果不叫沉浸式狀態欄,也完全沒有沉浸式狀態欄這種說法,我們估且可以把它叫做透明狀態欄效果吧。
隱藏狀態欄:
setContentView(R.layout.activity_main); //再該方法後執行 if (Build.VERSION.SDK_INT >= 21) { View decorView = getWindow().getDecorView(); int option = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE; decorView.setSystemUiVisibility(option); getWindow().setStatusBarColor(Color.TRANSPARENT); } ActionBar actionBar = getSupportActionBar(); actionBar.hide();
具體的沉浸效果該如何實現呢,系統提供實現沉浸式狀態欄的方法,通過WindowManager來實現,可分為兩步:
1. 在需要實現沉浸式狀態欄的Activity的布局中添加以下參數
android:fitsSystemWindows="true" android:clipToPadding="true"
2. 在Activity的setContentView()方法後面調用初始化的方法即可。
private void initState() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { //透明狀態欄 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); //透明導航欄 getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); } }
當上述的實現效果,其實並不好, 沒有在布局中設置clipToPadding為true的時候,會對應用的頂部Toolbar進行拉伸,在布局中兩個參數都進行設置後,頂部狀態欄變成了白色。這樣,我在github上找到一個很好的沉浸狀態欄效果,來看一下。
首先添加依賴,導入下面的包。有時候可能會出現版本不統一的問題,依次保證聯網的情況下點擊一下同步android studio會自動下載包。
compile 'com.jaeger.statusbaruitl:library:1.2.5'
在自定義控件中實現的進本邏輯,代碼較長。
package com.xiaoyuan; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.os.Build; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.animation.AlphaAnimation; import android.widget.ScrollView; import java.util.ArrayList; /** * @author Emil Sj�lander - [email protected] */ public class StickyScrollView extends ScrollView { /** * Tag for views that should stick and have constant drawing. e.g. * TextViews, ImageViews etc */ public static final String STICKY_TAG = "sticky"; /** * Flag for views that should stick and have non-constant drawing. e.g. * Buttons, ProgressBars etc */ public static final String FLAG_NONCONSTANT = "-nonconstant"; /** * Flag for views that have aren't fully opaque */ public static final String FLAG_HASTRANSPARANCY = "-hastransparancy"; /** * Default height of the shadow peeking out below the stuck view. */ private static final int DEFAULT_SHADOW_HEIGHT = 10; // dp; /** * XKJ add for add 50dp offset of top */ private static int MIN_STICK_TOP = 100;// px // private static final int MIN_STICK_TOP = 0; private ArrayList<View> stickyViews; private View currentlyStickingView; private float stickyViewTopOffset; private int stickyViewLeftOffset; private boolean redirectTouchesToStickyView; private boolean clippingToPadding; private boolean clipToPaddingHasBeenSet; private int mShadowHeight; private Drawable mShadowDrawable; private OnScrollChangedListener mOnScrollHandler = null; private IOnScrollToEnd mOnScrollToEnd = null; private final Runnable invalidateRunnable = new Runnable() { @Override public void run() { if (currentlyStickingView != null) { int l = getLeftForViewRelativeOnlyChild(currentlyStickingView); int t = getBottomForViewRelativeOnlyChild(currentlyStickingView); int r = getRightForViewRelativeOnlyChild(currentlyStickingView); int b = (int) (getScrollY() + (currentlyStickingView.getHeight() + stickyViewTopOffset)); invalidate(l, t, r, b); } postDelayed(this, 16); } }; public StickyScrollView(Context context) { this(context, null); } public StickyScrollView(Context context, AttributeSet attrs) { this(context, attrs, android.R.attr.scrollViewStyle); } public StickyScrollView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); setup(); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.StickyScrollView, defStyle, 0); final float density = context.getResources().getDisplayMetrics().density; int defaultShadowHeightInPix = (int) (DEFAULT_SHADOW_HEIGHT * density + 0.5f); mShadowHeight = a.getDimensionPixelSize(R.styleable.StickyScrollView_stuckShadowHeight, defaultShadowHeightInPix); int shadowDrawableRes = a.getResourceId(R.styleable.StickyScrollView_stuckShadowDrawable, -1); if (shadowDrawableRes != -1) { mShadowDrawable = context.getResources().getDrawable(shadowDrawableRes); } a.recycle(); } /** * Sets the height of the shadow drawable in pixels. * * @param height */ public void setShadowHeight(int height) { mShadowHeight = height; } public void setup() { stickyViews = new ArrayList<View>(); } private int getLeftForViewRelativeOnlyChild(View v) { int left = v.getLeft(); while (v.getParent() != getChildAt(0)) { v = (View) v.getParent(); left += v.getLeft(); } return left; } private int getTopForViewRelativeOnlyChild(View v) { int top = v.getTop(); while (v.getParent() != getChildAt(0)) { v = (View) v.getParent(); top += v.getTop(); } return top; } private int getRightForViewRelativeOnlyChild(View v) { int right = v.getRight(); while (v.getParent() != getChildAt(0)) { v = (View) v.getParent(); right += v.getRight(); } return right; } private int getBottomForViewRelativeOnlyChild(View v) { int bottom = v.getBottom(); while (v.getParent() != getChildAt(0)) { v = (View) v.getParent(); bottom += v.getBottom(); } return bottom; } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); if (!clipToPaddingHasBeenSet) { clippingToPadding = true; } notifyHierarchyChanged(); } @Override public void setClipToPadding(boolean clipToPadding) { super.setClipToPadding(clipToPadding); clippingToPadding = clipToPadding; clipToPaddingHasBeenSet = true; } @Override public void addView(View child) { super.addView(child); findStickyViews(child); } @Override public void addView(View child, int index) { super.addView(child, index); findStickyViews(child); } @Override public void addView(View child, int index, android.view.ViewGroup.LayoutParams params) { super.addView(child, index, params); findStickyViews(child); } @Override public void addView(View child, int width, int height) { super.addView(child, width, height); findStickyViews(child); } @Override public void addView(View child, android.view.ViewGroup.LayoutParams params) { super.addView(child, params); findStickyViews(child); } @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); if (currentlyStickingView != null) { canvas.save(); canvas.translate(getPaddingLeft() + stickyViewLeftOffset, getScrollY() + stickyViewTopOffset + (clippingToPadding ? getPaddingTop() : 0)); canvas.clipRect(0, (clippingToPadding ? -stickyViewTopOffset : 0), getWidth() - stickyViewLeftOffset, currentlyStickingView.getHeight() + mShadowHeight + 1); if (mShadowDrawable != null) { int left = 0; int right = currentlyStickingView.getWidth(); int top = currentlyStickingView.getHeight(); int bottom = currentlyStickingView.getHeight() + mShadowHeight; mShadowDrawable.setBounds(left, top, right, bottom); mShadowDrawable.draw(canvas); } canvas.clipRect(0, (clippingToPadding ? -stickyViewTopOffset : 0), getWidth(), currentlyStickingView.getHeight()); if (getStringTagForView(currentlyStickingView).contains(FLAG_HASTRANSPARANCY)) { showView(currentlyStickingView); currentlyStickingView.draw(canvas); hideView(currentlyStickingView); } else { currentlyStickingView.draw(canvas); } canvas.restore(); } } @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { redirectTouchesToStickyView = true; } if (redirectTouchesToStickyView) { redirectTouchesToStickyView = currentlyStickingView != null; if (redirectTouchesToStickyView) { redirectTouchesToStickyView = ev.getY() <= (currentlyStickingView.getHeight() + stickyViewTopOffset) && ev.getX() >= getLeftForViewRelativeOnlyChild(currentlyStickingView) && ev.getX() <= getRightForViewRelativeOnlyChild(currentlyStickingView); } } else if (currentlyStickingView == null) { redirectTouchesToStickyView = false; } if (redirectTouchesToStickyView) { ev.offsetLocation(0, -1 * ((getScrollY() + stickyViewTopOffset) - getTopForViewRelativeOnlyChild(currentlyStickingView))); // XKJ add TODO: remove this currentlyStickingView.invalidate(); } return super.dispatchTouchEvent(ev); } private boolean hasNotDoneActionDown = true; @Override public boolean onTouchEvent(MotionEvent ev) { if (redirectTouchesToStickyView) { ev.offsetLocation(0, ((getScrollY() + stickyViewTopOffset) - getTopForViewRelativeOnlyChild(currentlyStickingView))); } if (ev.getAction() == MotionEvent.ACTION_DOWN) { hasNotDoneActionDown = false; } if (hasNotDoneActionDown) { MotionEvent down = MotionEvent.obtain(ev); down.setAction(MotionEvent.ACTION_DOWN); super.onTouchEvent(down); hasNotDoneActionDown = false; } if (ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_CANCEL) { hasNotDoneActionDown = true; } return super.onTouchEvent(ev); } @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); doTheStickyThing(); if (mOnScrollHandler != null) { mOnScrollHandler.onScrollChanged(l, t, oldl, oldt); } int maxScroll = getChildAt(0).getHeight() - getHeight(); if (getChildCount() > 0 && t == maxScroll) { if (mOnScrollToEnd != null) { mOnScrollToEnd.onScrollToEnd(); } } } public void setOnScrollListener(OnScrollChangedListener handler) { mOnScrollHandler = handler; } public interface OnScrollChangedListener { public void onScrollChanged(int l, int t, int oldl, int oldt); } public interface IOnScrollToEnd { public void onScrollToEnd(); } public void setOnScrollToEndListener(IOnScrollToEnd handler) { mOnScrollToEnd = handler; } private void doTheStickyThing() { View viewThatShouldStick = null; View approachingView = null; for (View v : stickyViews) { int viewTop = getTopForViewRelativeOnlyChild(v) - getScrollY() + (clippingToPadding ? 0 : getPaddingTop()) - MIN_STICK_TOP;// add 50dp if (viewTop <= 0) { if (viewThatShouldStick == null || viewTop > (getTopForViewRelativeOnlyChild(viewThatShouldStick) - getScrollY() + (clippingToPadding ? 0 : getPaddingTop()))) { viewThatShouldStick = v; } } else { if (approachingView == null || viewTop < (getTopForViewRelativeOnlyChild(approachingView) - getScrollY() + (clippingToPadding ? 0 : getPaddingTop()))) { approachingView = v; } } } if (viewThatShouldStick != null) { stickyViewTopOffset = approachingView == null ? MIN_STICK_TOP : Math.min(MIN_STICK_TOP, getTopForViewRelativeOnlyChild(approachingView) - getScrollY() + (clippingToPadding ? 0 : getPaddingTop()) - viewThatShouldStick.getHeight()); if (viewThatShouldStick != currentlyStickingView) { if (currentlyStickingView != null) { stopStickingCurrentlyStickingView(); } // only compute the left offset when we start sticking. stickyViewLeftOffset = getLeftForViewRelativeOnlyChild(viewThatShouldStick); startStickingView(viewThatShouldStick); } } else if (currentlyStickingView != null) { stopStickingCurrentlyStickingView(); } } private void startStickingView(View viewThatShouldStick) { currentlyStickingView = viewThatShouldStick; if (getStringTagForView(currentlyStickingView).contains(FLAG_HASTRANSPARANCY)) { hideView(currentlyStickingView); } if (((String) currentlyStickingView.getTag()).contains(FLAG_NONCONSTANT)) { post(invalidateRunnable); } } private void stopStickingCurrentlyStickingView() { if (getStringTagForView(currentlyStickingView).contains(FLAG_HASTRANSPARANCY)) { showView(currentlyStickingView); } currentlyStickingView = null; removeCallbacks(invalidateRunnable); } /** * Notify that the sticky attribute has been added or removed from one or * more views in the View hierarchy */ public void notifyStickyAttributeChanged() { notifyHierarchyChanged(); } private void notifyHierarchyChanged() { if (currentlyStickingView != null) { stopStickingCurrentlyStickingView(); } stickyViews.clear(); findStickyViews(getChildAt(0)); doTheStickyThing(); invalidate(); } private void findStickyViews(View v) { if (v instanceof ViewGroup) { ViewGroup vg = (ViewGroup) v; for (int i = 0; i < vg.getChildCount(); i++) { String tag = getStringTagForView(vg.getChildAt(i)); if (tag != null && tag.contains(STICKY_TAG)) { stickyViews.add(vg.getChildAt(i)); } else if (vg.getChildAt(i) instanceof ViewGroup) { findStickyViews(vg.getChildAt(i)); } } } else { String tag = (String) v.getTag(); if (tag != null && tag.contains(STICKY_TAG)) { stickyViews.add(v); } } } private String getStringTagForView(View v) { Object tagObject = v.getTag(); return String.valueOf(tagObject); } private void hideView(View v) { if (Build.VERSION.SDK_INT >= 11) { v.setAlpha(0); } else { AlphaAnimation anim = new AlphaAnimation(1, 0); anim.setDuration(0); anim.setFillAfter(true); v.startAnimation(anim); } } private void showView(View v) { if (Build.VERSION.SDK_INT >= 11) { v.setAlpha(1); } else { AlphaAnimation anim = new AlphaAnimation(0, 1); anim.setDuration(0); anim.setFillAfter(true); v.startAnimation(anim); } } /** * 設置懸浮高度 * @param height */ public void setStickTop(int height) { MIN_STICK_TOP = height; } /** * 解決vviewpager在scrollview滑動沖突的問題 */ // 滑動距離及坐標 private float xDistance, yDistance, xLast, yLast; @Override public boolean onInterceptTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: xDistance = yDistance = 0f; xLast = ev.getX(); yLast = ev.getY(); break; case MotionEvent.ACTION_MOVE: final float curX = ev.getX(); final float curY = ev.getY(); xDistance += Math.abs(curX - xLast); yDistance += Math.abs(curY - yLast); // com.ihaveu.utils.Log.i("test", "curx:"+curX+",cury:"+curY+",xlast:"+xLast+",ylast:"+yLast); // xLast = curX; // yLast = curY; if (xDistance > yDistance) { return false; } } return super.onInterceptTouchEvent(ev); } }
接下來是調用自定義控件了,用到兩個關鍵的方法。StatusBarUtil.setTranslucentForImageView(MainActivity.this, 0, title)和llTitle.setBackgroundColor(Color.argb((int) alpha, 227, 29, 26))分別設置狀態欄和標題欄的顏色。
package com.xiaoyuan; import android.graphics.Color; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.view.ViewTreeObserver; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; import com.jaeger.library.StatusBarUtil; public class MainActivity extends AppCompatActivity implements View.OnClickListener, StickyScrollView.OnScrollChangedListener { TextView oneTextView, twoTextView; private StickyScrollView stickyScrollView; private int height; private LinearLayout llContent; private RelativeLayout llTitle; private FrameLayout frameLayout; private TextView title; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); initListeners(); } /** * 初始化View */ private void initView() { stickyScrollView = (StickyScrollView) findViewById(R.id.scrollView); frameLayout = (FrameLayout) findViewById(R.id.tabMainContainer); title = (TextView) findViewById(R.id.title); oneTextView = (TextView) findViewById(R.id.infoText); llContent = (LinearLayout) findViewById(R.id.ll_content); llTitle = (RelativeLayout) findViewById(R.id.ll_good_detail); oneTextView.setOnClickListener(this); twoTextView = (TextView) findViewById(R.id.secondText); twoTextView.setOnClickListener(this); stickyScrollView.setOnScrollListener(this); StatusBarUtil.setTranslucentForImageView(MainActivity.this, 0, title); FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) llTitle.getLayoutParams(); params.setMargins(0, getStatusHeight(), 0, 0); llTitle.setLayoutParams(params); //默認設置一個Frg getSupportFragmentManager().beginTransaction().replace(R.id.tabMainContainer, Fragment.newInstance()).commit(); } /** * 獲取狀態欄高度 * * @return */ private int getStatusHeight() { int resourceId = MainActivity.this.getResources().getIdentifier("status_bar_height", "dimen", "android"); return getResources().getDimensionPixelSize(resourceId); } @Override public void onClick(View v) { if (v.getId() == R.id.infoText) { getSupportFragmentManager().beginTransaction().replace(R.id.tabMainContainer, Fragment.newInstance()).commit(); } else if (v.getId() == R.id.secondText) { getSupportFragmentManager().beginTransaction().replace(R.id.tabMainContainer, Fragment1.newInstance()).commit(); } } private void initListeners() { //獲取內容總高度 final ViewTreeObserver vto = llContent.getViewTreeObserver(); vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { height = llContent.getHeight(); //注意要移除 llContent.getViewTreeObserver() .removeGlobalOnLayoutListener(this); } }); //獲取Fragment高度 ViewTreeObserver viewTreeObserver = frameLayout.getViewTreeObserver(); viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { height = height - frameLayout.getHeight(); //注意要移除 frameLayout.getViewTreeObserver() .removeGlobalOnLayoutListener(this); } }); //獲取title高度 ViewTreeObserver viewTreeObserver1 = llTitle.getViewTreeObserver(); viewTreeObserver1.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { height = height - llTitle.getHeight() - getStatusHeight();//計算滑動的總距離 stickyScrollView.setStickTop(llTitle.getHeight() + getStatusHeight());//設置距離多少懸浮 //注意要移除 llTitle.getViewTreeObserver() .removeGlobalOnLayoutListener(this); } }); } @Override public void onScrollChanged(int l, int t, int oldl, int oldt) { if (t <= 0) { llTitle.setBackgroundColor(Color.argb((int) 0, 255, 255, 255)); } else if (t > 0 && t <= height) { float scale = (float) t / height; int alpha = (int) (255 * scale); llTitle.setBackgroundColor(Color.argb((int) alpha, 227, 29, 26));//設置標題欄的透明度及顏色 StatusBarUtil.setTranslucentForImageView(MainActivity.this, alpha, title);//設置狀態欄的透明度 } else { StatusBarUtil.setTranslucentForImageView(MainActivity.this, 0, title); llTitle.setBackgroundColor(Color.argb((int) 255, 227, 29, 26)); StatusBarUtil.setTranslucentForImageView(MainActivity.this, 255, title); } } }
最後,尊重一下上述代碼的原作者,具體代碼可到github下載,https://github.com/xiaoyuanandroid/ProductPage。
面面具到!android重力傳感器,前兩篇都是向大家介紹了很有意思的兩種手勢操作,嵌入我們游戲中,不得不說讓游戲的自由度、可玩性和趣味性都增色不少!那麼今天繼續給大家介紹
Android自定義Dialog簡單實例 做Android應用中,最缺少不了的就是自定義Dialog,對於系統默認提供的Dialog樣式,一般都不復合我們應用的樣式。
linux2.4.18----25.文件系統的構建一. 文件系統的構建1.busybox的編譯方法: 用虛擬機的redhat9.0進行編譯版本: busybox-1.00
Android網絡編程(一)HTTP協議原理 前言 這篇文章是這個系列的開篇,作為移動開發者,開發的應用不免會對網絡進行訪問,雖然現在已經有很多的開源庫幫助我們可以輕而易
Android studio使用gradle動態構建APP(不同的包,不