Android进阶——自定义View之扩展系统Dialog
来源:互联网 发布:编程教学 编辑:程序博客网 时间:2024/05/02 01:21
引言
今天给大家总结有关自定义对话框的相关内容,前面文章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(); }
0 0
- Android进阶——自定义View之扩展系统Dialog
- Android进阶——自定义View之扩展系统控件的另一种思路实现渐变文字动画的TextView
- Android进阶——自定义View之重写ViewGroup组合系统控件实现自定义ToolBar模板
- Android进阶——自定义View之组合系统控件实现水珠形状的ItemView
- Android进阶之——自定义view(一)
- Android进阶——自定义View之必学的系统控件架构及自定义控件概述
- Android自定义view之弹出式dialog
- Android进阶——自定义View之View的绘制流程及实现onMeasure完全攻略
- 《Android进阶之光》学习笔记——第三章 View体系与自定义View
- [android进阶]自定义View之TopBar
- Android开发进阶之自定义view
- Android 自定义View绘图篇之进阶
- Android进阶之自定义view(一)
- Android进阶之自定义view(二)
- Android进阶之自定义view(三)
- Android进阶之自定义view(四)
- Android 自定义View动画篇之进阶
- 【Android进阶之自定义View(一)】
- 详细介绍Screenshot Reader支持的语言和文档格式
- Arch Linux 安装小记
- 使用Socket发送Http请求
- 多线程学习
- 使用Socket发送Http请求
- Android进阶——自定义View之扩展系统Dialog
- 虚拟机不能启动,E_FAIL(0x80004005) 及其处理
- md5加密 控制台传入与web传入参数 md5加密结果不同
- Struts2实现文件上传和下载
- 需求管理和控制方面,项目经理、产品经理应怎么配合?
- 机器学习----SVM(3)核函数
- 网页书签
- Java NIO Socket通信
- POJ 3050-Hopscotch(BFS)