Android进阶——自定义View之扩展系统Dialog
2016-05-10 15:21
881 查看
引言
今天给大家总结有关自定义对话框的相关内容,前面文章Android入门——AlertDialog和ProgressDialog总结 都在在利用系统提供的函数来实现对话框,但局限性太大,当我们想自己定义Dialog视图的时候,就不能利用系统函数了,就需要我们这里的自定义对话框了来满足产品经理的各种idea。一、Dialog部分源码结构
学习下源码的编程风格和规范/** * Base class for Dialogs. * Activity提供了一系列的方法用于dialog的管理:onCreateDialog(int)、onPrepareDialog(int)、showDialog(int)、dismissDialog(int)、 *当这些方法被调用之后我们就可以通过 getOwnerActivity()方法获取得到对应的Dialog依附的Activity * *设置在Dialog中隐藏软键盘 * getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, * WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); */ public class Dialog implements DialogInterface, Window.Callback, KeyEvent.Callback, OnCreateContextMenuListener, Window.OnWindowDismissedCallback { private Activity mOwnerActivity; final Context mContext; final WindowManager mWindowManager; Window mWindow; View mDecor; private ActionBar mActionBar; protected boolean mCancelable = true; private String mCancelAndDismissTaken; private Message mCancelMessage,mDismissMessage,mShowMessage; private OnKeyListener mOnKeyListener; private boolean mCreated = false,mShowing = false,mCanceled = false; private final Handler mHandler = new Handler(); private static final int DISMISS = 0x43,CANCEL = 0x44,SHOW = 0x45; private Handler mListenersHandler; private SearchEvent mSearchEvent; private ActionMode mActionMode; private int mActionModeTypeStarting = ActionMode.TYPE_PRIMARY; private final Runnable mDismissAction = new Runnable() { public void run() { dismissDialog(); } }; public Dialog(@NonNull Context context) { this(context, 0, true); } public Dialog(@NonNull Context context, @StyleRes int themeResId) { this(context, themeResId, true); } Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) { if (createContextThemeWrapper) { if (themeResId == 0) { final TypedValue outValue = new TypedValue(); context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true); themeResId = outValue.resourceId; } mContext = new ContextThemeWrapper(context, themeResId); } else { mContext = context; } mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); final Window w = new PhoneWindow(mContext); mWindow = w; w.setCallback(this); w.setOnWindowDismissedCallback(this); w.setWindowManager(mWindowManager, null, null); w.setGravity(Gravity.CENTER); mListenersHandler = new ListenersHandler(this); } /** * @deprecated * @hide */ @Deprecated protected Dialog(@NonNull Context context, boolean cancelable, Message cancelCallback) { this(context); mCancelable = cancelable; mCancelMessage = cancelCallback; } protected Dialog(@NonNull Context context, boolean cancelable, OnCancelListener cancelListener) { this(context); mCancelable = cancelable; setOnCancelListener(cancelListener); } /** * Retrieve the Context this Dialog is running in. * @return Context The Context used by the Dialog. */ @NonNull public final Context getContext() { return mContext; } /** * Sets the Activity that owns this dialog. An example use: This Dialog will * use the suggested volume control stream of the Activity. * @param activity The Activity that owns this dialog. */ public final void setOwnerActivity(Activity activity) { mOwnerActivity = activity; getWindow().setVolumeControlStream(mOwnerActivity.getVolumeControlStream()); } /** * Returns the Activity that owns this Dialog. For example, if * {@link Activity#showDialog(int)} is used to show this Dialog, that * Activity will be the owner (by default). Depending on how this dialog was * created, this may return null. * * @return The Activity that owns this Dialog. */ public final Activity getOwnerActivity() { return mOwnerActivity; } public boolean isShowing() { return mShowing; } public void create() { if (!mCreated) { dispatchOnCreate(null); } } public void show() { if (DBG) { Log.d(TAG, "show"); } if (mShowing) { if (mDecor != null) { if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) { mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR); } mDecor.setVisibility(View.VISIBLE); } return; } mCanceled = false; if (!mCreated) { dispatchOnCreate(null); } onStart(); mDecor = mWindow.getDecorView(); if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) { final ApplicationInfo info = mContext.getApplicationInfo(); mWindow.setDefaultIcon(info.icon); mWindow.setDefaultLogo(info.logo); mActionBar = new WindowDecorActionBar(this); } WindowManager.LayoutParams l = mWindow.getAttributes(); if ((l.softInputMode & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) { WindowManager.LayoutParams nl = new WindowManager.LayoutParams(); nl.copyFrom(l); nl.softInputMode |= WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION; l = nl; } try { mWindowManager.addView(mDecor, l); mShowing = true; sendShowMessage(); } finally { } } /** * Hide the dialog, but do not dismiss it. */ public void hide() { if (mDecor != null) { mDecor.setVisibility(View.GONE); } } /** * 关闭并删除Dialog,线程安全,不能重写 */ @Override public void dismiss() { if (Looper.myLooper() == mHandler.getLooper()) { dismissDialog(); } else { mHandler.post(mDismissAction); } } void dismissDialog() { if (DBG) { Log.d(TAG, "dismissDialog"); } if (mDecor == null || !mShowing) { return; } if (mWindow.isDestroyed()) { Log.e(TAG, "Tried to dismissDialog() but the Dialog's window was already destroyed!"); return; } try { mWindowManager.removeViewImmediate(mDecor); } finally { if (mActionMode != null) { mActionMode.finish(); } mDecor = null; mWindow.closeAllPanels(); onStop(); mShowing = false; sendDismissMessage(); } } private void sendDismissMessage() { if (mDismissMessage != null) { Message.obtain(mDismissMessage).sendToTarget(); } } private void sendShowMessage() { if (mShowMessage != null) { // Obtain a new message so this dialog can be re-used Message.obtain(mShowMessage).sendToTarget(); } } /** *与Activity的类似,初始化Dialog,包括调用 setContentView *如果Dialog在Activity之前关闭,那么会调用onSaveInstanceState保留状态 * @param savedInstanceState If this dialog is being reinitalized after a */ protected void onCreate(Bundle savedInstanceState) { } /** * Called when the dialog is starting. */ protected void onStart() { if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(true); } /** * Called to tell you that you're stopping. */ protected void onStop() { if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false); } private static final String DIALOG_SHOWING_TAG = "android:dialogShowing"; private static final String DIALOG_HIERARCHY_TAG = "android:dialogHierarchy"; public Bundle onSaveInstanceState() { Bundle bundle = new Bundle(); bundle.putBoolean(DIALOG_SHOWING_TAG, mShowing); if (mCreated) { bundle.putBundle(DIALOG_HIERARCHY_TAG, mWindow.saveHierarchyState()); } return bundle; } public void onRestoreInstanceState(Bundle savedInstanceState) { final Bundle dialogHierarchyState = savedInstanceState.getBundle(DIALOG_HIERARCHY_TAG); if (dialogHierarchyState == null) { // dialog has never been shown, or onCreated, nothing to restore. return; } dispatchOnCreate(savedInstanceState); mWindow.restoreHierarchyState(dialogHierarchyState); if (savedInstanceState.getBoolean(DIALOG_SHOWING_TAG)) { show(); } } public Window getWindow() { return mWindow; } public View getCurrentFocus() { return mWindow != null ? mWindow.getCurrentFocus() : null; } @Nullable public View findViewById(@IdRes int id) { return mWindow.findViewById(id); } public void setContentView(@LayoutRes int layoutResID) { mWindow.setContentView(layoutResID); } public void setContentView(View view) { mWindow.setContentView(view); } public void setContentView(View view, ViewGroup.LayoutParams params) { mWindow.setContentView(view, params); } public void addContentView(View view, ViewGroup.LayoutParams params) { mWindow.addContentView(view, params); } public void setTitle(CharSequence title) { mWindow.setTitle(title); mWindow.getAttributes().setTitle(title); } public void setTitle(@StringRes int titleId) { setTitle(mContext.getText(titleId)); } public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { event.startTracking(); return true; } return false; } public boolean onKeyLongPress(int keyCode, KeyEvent event) { return false; } public boolean onKeyUp(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking() && !event.isCanceled()) { onBackPressed(); return true; } return false; } public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { return false; } public void onBackPressed() { if (mCancelable) { cancel(); } } public boolean onKeyShortcut(int keyCode, KeyEvent event) { return false; } public boolean onTouchEvent(MotionEvent event) { if (mCancelable && mShowing && mWindow.shouldCloseOnTouch(mContext, event)) { cancel(); return true; } return false; } public boolean onTrackballEvent(MotionEvent event) { return false; } public void onWindowAttributesChanged(WindowManager.LayoutParams params) { if (mDecor != null) { mWindowManager.updateViewLayout(mDecor, params); } } public void onContentChanged() { } public void onWindowFocusChanged(boolean hasFocus) { } public void onAttachedToWindow() { } public void onDetachedFromWindow() { } /** @hide */ @Override public void onWindowDismissed() { dismiss(); } /** * 事件传递 :用于处理各种事件,我们可以重写key events在传递到window之前 * @param event The key event. */ public boolean dispatchKeyEvent(KeyEvent event) { if ((mOnKeyListener != null) && (mOnKeyListener.onKey(this, event.getKeyCode(), event))) { return true; } if (mWindow.superDispatchKeyEvent(event)) { return true; } return event.dispatch(this, mDecor != null ? mDecor.getKeyDispatcherState() : null, this); } public boolean dispatchKeyShortcutEvent(KeyEvent event) { if (mWindow.superDispatchKeyShortcutEvent(event)) { return true; } return onKeyShortcut(event.getKeyCode(), event); } public boolean dispatchTouchEvent(MotionEvent ev) { if (mWindow.superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); } public final boolean requestWindowFeature(int featureId) { return getWindow().requestFeature(featureId); } public final void setFeatureDrawableResource(int featureId, @DrawableRes int resId) { getWindow().setFeatureDrawableResource(featureId, resId); } public final void setFeatureDrawableUri(int featureId, Uri uri) { getWindow().setFeatureDrawableUri(featureId, uri); } public final void setFeatureDrawable(int featureId, Drawable drawable) { getWindow().setFeatureDrawable(featureId, drawable); } public final void setFeatureDrawableAlpha(int featureId, int alpha) { getWindow().setFeatureDrawableAlpha(featureId, alpha); } public LayoutInflater getLayoutInflater() { return getWindow().getLayoutInflater(); } /** *各种监听器 **/ public void setOnCancelListener(final OnCancelListener listener) { if (mCancelAndDismissTaken != null) { throw new IllegalStateException( "OnCancelListener is already taken by " + mCancelAndDismissTaken + " and can not be replaced."); } if (listener != null) { mCancelMessage = mListenersHandler.obtainMessage(CANCEL, listener); } else { mCancelMessage = null; } } public void setOnDismissListener(final OnDismissListener listener) { if (mCancelAndDismissTaken != null) { throw new IllegalStateException( "OnDismissListener is already taken by " + mCancelAndDismissTaken + " and can not be replaced."); } if (listener != null) { mDismissMessage = mListenersHandler.obtainMessage(DISMISS, listener); } else { mDismissMessage = null; } } public void setOnShowListener(OnShowListener listener) { if (listener != null) { mShowMessage = mListenersHandler.obtainMessage(SHOW, listener); } else { mShowMessage = null; } } /** * Sets the callback that will be called if a key is dispatched to the dialog. */ public void setOnKeyListener(final OnKeyListener onKeyListener) { mOnKeyListener = onKeyListener; } private static final class ListenersHandler extends Handler { private WeakReference<DialogInterface> mDialog; public ListenersHandler(Dialog dialog) { mDialog = new WeakReference<DialogInterface>(dialog); } @Override public void handleMessage(Message msg) { switch (msg.what) { case DISMISS: ((OnDismissListener) msg.obj).onDismiss(mDialog.get()); break; case CANCEL: ((OnCancelListener) msg.obj).onCancel(mDialog.get()); break; case SHOW: ((OnShowListener) msg.obj).onShow(mDialog.get()); break; } } } }
二、扩展系统Dialog步骤
定义Dialog将要显示的布局xml文件继承Dialog 并实现相关的构造方法
重写相关父类的生命周期方法,比如说在onCreate里通过LayoutInflater的LayoutInflater对象的inflate方法把xml布局文件映射成为自定义的view,获取我们自定义对话框的view,然后利用setContentView
1、实现普通的自定义Dialog
定义自定义Dialog 的布局xml<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView style="@style/TitleStyle" android:layout_width="match_parent" android:padding="5dp" android:layout_height="wrap_content" android:gravity="center" android:text="自定义的Dialog" android:drawableLeft="@mipmap/ic_red_launcher"/> <ProgressBar android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout>
继承Dialog并实现相关构造方法并重写onCreate:
/** * Created by cmo on 16-5-5. */ public class MyCostomDialog extends Dialog { Context mContext; public MyCostomDialog(Context context) { super(context); mContext=context; } /** *另一种形式的使用Theme * public MyCostomDialog(Context context){ super(context,R.style.myCustomdialog); mContext=context; } */ //使用到Theme时调用 public MyCostomDialog(Context context, int theme) { super(context, theme); mContext = context; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); LayoutInflater inflater = (LayoutInflater) mContext .getSystemService(Context.LAYOUT_INFLATER_SERVICE); View layout = inflater.inflate(R.layout.dialog_my_costom, null); this.setContentView(layout); } }
styles.xml:
<resources> <!-- Base application theme. --> <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <!-- Customize your theme here. --> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> </style> <style name="TitleStyle"> <item name="android:textColor">@color/colorGreen</item> <item name="android:textSize">@dimen/activity_title_size</item> </style> <!-- android:windowFrame:界面对应的前景图片; android:background:背景图片 android:windowIsFloating:表示浮在屏幕上的,如果在这里使用了,整个layout就会在 屏幕中心,相当于浮在屏幕上,所以这个只适用于dialog android:windowContentOverlay:表示标题栏的阴影部分的样式,使用图片或者颜色 android:windowNoTitle:标题栏是否隐藏 --> <style name="myCustomdialog" parent="android:Theme.Dialog"> <item name="android:windowFrame">@null</item> <item name="android:windowIsFloating">true</item> <item name="android:windowContentOverlay">@null</item> <item name="android:windowNoTitle">true</item> <item name="android:background">@drawable/shape_dialog_bcg</item> </style> </resources>
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } //调用代码显示无Theme的Dialog public void showDialog(View view){ MyCostomDialog dialog = new MyCostomDialog(MainActivity.this); ///MyCostomDialog dialog = new MyCostomDialog(MainActivity.this, R.style.myCustomdialog);显示自定义Theme的的Dialog dialog.show(); } }
2、显示具有交互功能的Dialog
布局很简单就不贴了,既然需要交互,那肯定得注册监听,so 参考Activity上的处理肯定是在界面初始化的时候设置对应的监听/** * Created by cmo on 16-5-5. */ public class MyCostomDialog extends Dialog { Context mContext; public MyCostomDialog(Context context) { super(context); mContext=context; } //使用到Theme时调用 public MyCostomDialog(Context context, int theme) { super(context, theme); mContext = context; } //设置RadioGroup的监听 private void setRadioListener(final View view){ final RadioGroup radioGroup= (RadioGroup) view.findViewById(R.id.id_radiogroup); radioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener(){ @Override public void onCheckedChanged(RadioGroup group, int checkedId) { RadioButton checkedRadio=(RadioButton)findViewById(radioGroup.getCheckedRadioButtonId()); switch (checkedId){ case R.id.id_android_radiobtn: case R.id.id_ios_radiobtn: case R.id.id_java_radiobtn: Log.d("TAG",checkedRadio.getText().toString()); break; default: break; } } }); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); LayoutInflater inflater = (LayoutInflater) mContext .getSystemService(Context.LAYOUT_INFLATER_SERVICE); View layout = inflater.inflate(R.layout.dialog_my_costom, null); setRadioListener(layout);//设置监听 this.setContentView(layout); } }
3、与Activity之间的通信
3.1、通过构造函数把Activity里的数据传递至Dialog
package com.crazymo.costomstyledialog; /** * Created by cmo on 16-5-5. */ public class MyCostomDialog extends Dialog { Context mContext; String mText; public MyCostomDialog(Context context) { super(context); mContext=context; } //使用到Theme时调用 public MyCostomDialog(Context context, int theme) { super(context, theme); mContext = context; } //其中txt则是从Activity传递至DIalog的数据 public MyCostomDialog(Context context, String txt,int theme) { super(context,theme); mContext = context; mText=txt; } private void setRadioListener(final View view){ final RadioGroup radioGroup= (RadioGroup) view.findViewById(R.id.id_radiogroup); radioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener(){ @Override public void onCheckedChanged(RadioGroup group, int checkedId) { RadioButton checkedRadio=(RadioButton)findViewById(radioGroup.getCheckedRadioButtonId()); switch (checkedId){ case R.id.id_android_radiobtn: case R.id.id_ios_radiobtn: case R.id.id_java_radiobtn: Log.d("TAG", checkedRadio.getText().toString()); break; default: break; } } }); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); LayoutInflater inflater = (LayoutInflater) mContext .getSystemService(Context.LAYOUT_INFLATER_SERVICE); View layout = inflater.inflate(R.layout.dialog_my_costom, null); setRadioListener(layout); TextView tv = (TextView)layout.findViewById(R.id.id_dialog_title_txt); tv.setText(mText);//设置Activity传递来的数据 this.setContentView(layout); } }
在MainActivity里显示:
public void showDialog(View view){ MyCostomDialog dialog = new MyCostomDialog(MainActivity.this, "我来自Activity",R.style.myCustomdialog); dialog.show(); }
3.2、通过回调方式把Dialog里的数据回传至Activity
package com.crazymo.costomstyledialog; /** * Created by cmo on 16-5-5. */ public class MyCostomDialog extends Dialog { Context mContext; String mText; private IShowPicByIndexListener mIShowPicByIndexListener;//声明一个回调接口函数变量,用于调用 //定义一个回调接口,用于接收Activity返回的值 public interface IShowPicByIndexListener{ public void onShowPicIndex(int index); } public MyCostomDialog(Context context) { super(context); mContext=context; } //使用到Theme时调用 public MyCostomDialog(Context context, int theme) { super(context, theme); mContext = context; } //把回调也传递到构造方法中 public MyCostomDialog(Context context, String txt,int theme,IShowPicByIndexListener listener) { super(context,theme); mContext = context; mText=txt; mIShowPicByIndexListener=listener; } public MyCostomDialog(Context context, String txt,int theme) { super(context,theme); mContext = context; mText=txt; } private void setRadioListener(final View view){ final RadioGroup radioGroup= (RadioGroup) view.findViewById(R.id.id_radiogroup); radioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener(){ @Override public void onCheckedChanged(RadioGroup group, int checkedId) { int index=0; RadioButton checkedRadio=(RadioButton)findViewById(radioGroup.getCheckedRadioButtonId()); switch (checkedId){ case R.id.id_android_radiobtn: index=1; break; case R.id.id_ios_radiobtn: index=2; break; case R.id.id_java_radiobtn: index=3; break; default: index=0; } mIShowPicByIndexListener.onShowPicIndex(index); } }); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); LayoutInflater inflater = (LayoutInflater) mContext .getSystemService(Context.LAYOUT_INFLATER_SERVICE); View layout = inflater.inflate(R.layout.dialog_my_costom, null); setRadioListener(layout); TextView tv = (TextView)layout.findViewById(R.id.id_dialog_title_txt); tv.setText(mText); this.setContentView(layout); } }
package com.crazymo.costomstyledialog; public class MainActivity extends Activity { private int[] imgs={R.mipmap.ic_blue_launcher,R.mipmap.ic_red_launcher,R.mipmap.ic_toy,R.mipmap.ic_launcher}; private RadioGroup mRadioGroup; private ImageView mImageView; private int index=0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); init(); } private void init(){ getViews(); } private void getViews(){ mImageView= (ImageView) findViewById(R.id.id_pic_imgv); } public void showDialog(View view){ MyCostomDialog dialog = new MyCostomDialog(MainActivity.this, "我来自Activity", R.style.myCustomdialog, new MyCostomDialog.IShowPicByIndexListener() { @Override public void onShowPicIndex(int index) { mImageView.setImageResource(imgs[index]); } }); dialog.show(); } private void setImageViewBcg(int index){ mImageView.setImageResource(imgs[index]); } }
4、自主控制自定义Dialog
4.1、动态设置Dialog的显示位置
对话框默认显示于屏幕中心位置,但是我们可以通过Window对象来动态设置Dialog的位置public void showDialog(View view){ MyCostomDialog dialog = new MyCostomDialog(MainActivity.this, "我来自Activity", R.style.myCustomdialog, new MyCostomDialog.IShowPicByIndexListener() { @Override public void onShowPicIndex(int index) { mImageView.setImageResource(imgs[index]); } }); Window window=dialog.getWindow();//获取Window对象 window.setGravity(Gravity.TOP|Gravity.LEFT);//设置显示位置 dialog.show(); }
4.2、改变对话框的透明度
改变对话框的透明度也可以通过设置Window的alpha属性。Window window=dialog.getWindow();//先获取Window对象 WindowManager.LayoutParams layoutParams=window.getAttributes();//再获取属性集 layoutParams.alpha=0.6f;//设置alpha属性 window.setAttributes(layoutParams);
4.3、自主控制对话框的关闭状态
public void showDialog(View view){ MyCostomDialog dialog = new MyCostomDialog(MainActivity.this, "我来自Activity", R.style.myCustomdialog, new MyCostomDialog.IShowPicByIndexListener() { @Override public void onShowPicIndex(int index) { mImageView.setImageResource(imgs[index]); } }); Window window=dialog.getWindow(); window.setGravity(Gravity.TOP | Gravity.LEFT); WindowManager.LayoutParams layoutParams=window.getAttributes(); layoutParams.alpha=0.6f; window.setAttributes(layoutParams); try { Field field=dialog.getClass().getSuperclass().getDeclaredField("mShowing"); field.setAccessible(true); try { field.set(dialog,false); } catch (IllegalAccessException e) { e.printStackTrace(); } } catch (NoSuchFieldException e) { e.printStackTrace(); } dialog.show(); }
相关文章推荐
- Service要点总结
- 迅雷下载Android Studio最新版本(Android Studio 2.1.1.0)
- android KITKAT 以上实现沉浸式
- 【Android】EventBus两分钟入门实例
- 【教程】【多图详解】如何在Eclipse中离线安装ADT(Android Development Tools)
- #android# service stopself() stopservice()
- 关于 Android 进程保活,你所需要知道的一切【转】
- android Connot reload AVD list问题解决方案
- 开源Android-PullToRefresh下拉刷新源码分析
- Android中shape的使用
- Android自动更新
- android分辨率适配
- Android多媒体框架图
- android圆形图加描边
- android onActivityResult()接收返回数据为null的解决方案
- Android 手动显示和隐藏软键盘
- Android中的广播Broadcast详解
- android 动画详解(一)
- Android开源库集锦
- Android实现跑马灯效果的TextView