热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Android两行代码实现换肤从appcompatv7原理出发

背景换肤方案原理在网上已经很多了,这里不再详细描述,强迫症的我总是想让提供给别人使用的SDK尽量好用,哪怕是给自己带来额外的工作量,经过一段时间的奋斗,实现了一个自我感觉良好的换肤

背景

换肤方案原理在网上已经很多了, 这里不再详细描述, 强迫症的我总是想让提供给别人使用的SDK尽量好用, 哪怕是给自己带来额外的工作量, 经过一段时间的奋斗, 实现了一个自我感觉良好的换肤框架.

这里主要来看看Android 源码中”com.android.support:appcompat-v7”包的实现, 以及源码思想在Android-skin-support中的应用 – 如何打造一款好用的换肤框架.


appcompat-v7包实现

首先来看一下源码的实现: 
AppCompatActivity源码

public class AppCompatActivity extends FragmentActivity {@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {final AppCompatDelegate delegate = getDelegate();delegate.installViewFactory();delegate.onCreate(savedInstanceState);...}@Overridepublic MenuInflater getMenuInflater() {return getDelegate().getMenuInflater();}@Overridepublic void setContentView(@LayoutRes int layoutResID) {getDelegate().setContentView(layoutResID);}@Overridepublic void setContentView(View view) {getDelegate().setContentView(view);}....
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

AppCompatActivity 将大部分生命周期委托给了AppCompatDelegate

再看看相关的类图 
这里写图片描述

AppCompateDelegate的子类AppCompatDelegateImplV9

class AppCompatDelegateImplV9 extends AppCompatDelegateImplBaseimplements MenuBuilder.Callback, LayoutInflaterFactory {@Overridepublic void installViewFactory() {LayoutInflater layoutInflater = LayoutInflater.from(mContext);if (layoutInflater.getFactory() == null) {LayoutInflaterCompat.setFactory(layoutInflater, this);} else {if (!(LayoutInflaterCompat.getFactory(layoutInflater)instanceof AppCompatDelegateImplV9)) {Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"+ " so we can not install AppCompat's");}}}
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

从这可以看出通过实现LayoutInflaterFactory接口来实现换肤至少可以支持到api 9以上

网上很多换肤框架的实现, 通过LayoutInflater.setFactory的方式, 在回调的onCreateView中解析每一个View的attrs, 判断是否有已标记需要换肤的属性, 比方说background, textColor, 或者说相应资源是否为skin_开头等等. 
然后保存到map中, 对每一个View做for循环去遍历所有的attr, 想要对更多的属性进行换肤, 需要Activity实现接口, 将需要换肤的View, 以及相应的属性收集到一起 
那么是不是能够寻求一种让使用者更方便的方式来实现, 做一个侵入性尽量小的框架呢?

本着开发者应有的好奇心, 深入的研究了一些v7包的实现 
onCreate
setContentView
AppCompatDelegateImplV9中, 在LayoutInflaterFactory的接口方法onCreateView 中将View的创建交给了AppCompatViewInflater

@Override
public final View onCreateView(View parent, String name,Context context, AttributeSet attrs) {// First let the Activity's Factory try and inflate the viewfinal View view = callActivityOnCreateView(parent, name, context, attrs);if (view != null) {return view;}// If the Factory didn't handle it, let our createView() method tryreturn createView(parent, name, context, attrs);
}@Override
public View createView(View parent, final String name, &#64;NonNull Context context,&#64;NonNull AttributeSet attrs) {final boolean isPre21 &#61; Build.VERSION.SDK_INT <21;if (mAppCompatViewInflater &#61;&#61; null) {mAppCompatViewInflater &#61; new AppCompatViewInflater();}// We only want the View to inherit its context if we&#39;re running pre-v21final boolean inheritContext &#61; isPre21 && shouldInheritContext((ViewParent) parent);return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,isPre21, /* Only read android:theme pre-L (L&#43; handles this anyway) */true, /* Read read app:theme as a fallback at all times for legacy reasons */VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */);
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

再来看一下AppCompatViewInflater中createView的实现

public final View createView(View parent, final String name, &#64;NonNull Context context,&#64;NonNull AttributeSet attrs, boolean inheritContext,boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {......View view &#61; null;switch (name) {case "TextView":view &#61; new AppCompatTextView(context, attrs);break;case "ImageView":view &#61; new AppCompatImageView(context, attrs);break;case "Button":view &#61; new AppCompatButton(context, attrs);break;case "EditText":view &#61; new AppCompatEditText(context, attrs);break;case "Spinner":view &#61; new AppCompatSpinner(context, attrs);break;case "ImageButton":view &#61; new AppCompatImageButton(context, attrs);break;case "CheckBox":view &#61; new AppCompatCheckBox(context, attrs);break;......}......return view;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

再看一下其中一个类AppCompatTextView的实现

public class AppCompatTextView extends TextView implements TintableBackgroundView {public AppCompatTextView(Context context, AttributeSet attrs, int defStyleAttr) {super(TintContextWrapper.wrap(context), attrs, defStyleAttr);mBackgroundTintHelper &#61; new AppCompatBackgroundHelper(this);mBackgroundTintHelper.loadFromAttributes(attrs, defStyleAttr);mTextHelper &#61; AppCompatTextHelper.create(this);mTextHelper.loadFromAttributes(attrs, defStyleAttr);mTextHelper.applyCompoundDrawablesTints();}&#64;Overridepublic void setBackgroundResource(&#64;DrawableRes int resId) {super.setBackgroundResource(resId);if (mBackgroundTintHelper !&#61; null) {mBackgroundTintHelper.onSetBackgroundResource(resId);}}......
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

AppCompatBackgroundHelper.Java

void loadFromAttributes(AttributeSet attrs, int defStyleAttr) {TintTypedArray a &#61; TintTypedArray.obtainStyledAttributes(mView.getContext(), attrs,R.styleable.ViewBackgroundHelper, defStyleAttr, 0);......if (a.hasValue(R.styleable.ViewBackgroundHelper_android_background)) {mBackgroundResId &#61; a.getResourceId(R.styleable.ViewBackgroundHelper_android_background, -1);ColorStateList tint &#61; mDrawableManager.getTintList(mView.getContext(), mBackgroundResId);if (tint !&#61; null) {setInternalBackgroundTint(tint);}}......
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

到这里我仿佛是发现了新大陆一样兴奋, 源码中可以通过拦截View创建过程, 替换一些基础的组件, 然后对一些特殊的属性(eg: background, textColor) 做处理, 那我们为什么不能将这种思想拿到换肤框架中来使用呢?


Android-skin-support换肤框架实现

抱着试一试不会少块肉的心情, 开始了我的换肤框架开发之路

先简单讲一下原理: 
1. 参照源码实现在Activity onCreate中为LayoutInflater setFactory, 将View的创建过程交给自定义的SkinCompatViewInflater类来实现 
2. 重写系统组件, 实现换肤接口, 表明该控件支持换肤, 并在View创建之后统一收集 
3. 在重写的View中解析出需要换肤的属性, 并保存ResId到成员变量 
4. 重写类似setBackgroundResource方法, 解析需要换肤的属性, 并保存变量 
5. applySkin 在切换皮肤的时候, 从皮肤资源中获取资源

下面说一个简单的例子(SkinCompatTextView): 
1. 实现SkinCompatSupportable接口 
2. 在构造方法中通过SkinCompatBackgroundHelper和SkinCompatTextHelper分别解析出background, textColor并保存 
3. 重写setBackgroundResource和setTextAppearance, 解析出对应的资源Id, 表明该控件支持从代码中设置资源, 且支持该资源换肤 
4. 在用户点击切换皮肤时调用applySkin方法设置皮肤

public interface SkinCompatSupportable {void applySkin();
}public class SkinCompatTextView extends AppCompatTextView implements SkinCompatSupportable {public SkinCompatTextView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);mBackgroundTintHelper &#61; new SkinCompatBackgroundHelper(this);mBackgroundTintHelper.loadFromAttributes(attrs, defStyleAttr);mTextHelper &#61; new SkinCompatTextHelper(this);mTextHelper.loadFromAttributes(attrs, defStyleAttr);}&#64;Overridepublic void setBackgroundResource(&#64;DrawableRes int resId) {super.setBackgroundResource(resId);if (mBackgroundTintHelper !&#61; null) {mBackgroundTintHelper.onSetBackgroundResource(resId);}}&#64;Overridepublic void setTextAppearance(Context context, int resId) {super.setTextAppearance(context, resId);if (mTextHelper !&#61; null) {mTextHelper.onSetTextAppearance(context, resId);}}&#64;Overridepublic void applySkin() {if (mBackgroundTintHelper !&#61; null) {mBackgroundTintHelper.applySkin();}if (mTextHelper !&#61; null) {mTextHelper.applySkin();}}
}public class SkinCompatTextHelper extends SkinCompatHelper {private static final String TAG &#61; SkinCompatTextHelper.class.getSimpleName();private final TextView mView;private int mTextColorResId &#61; INVALID_ID;private int mTextColorHintResId &#61; INVALID_ID;public SkinCompatTextHelper(TextView view) {mView &#61; view;}public void loadFromAttributes(AttributeSet attrs, int defStyleAttr) {final Context context &#61; mView.getContext();// First read the TextAppearance style idTintTypedArray a &#61; TintTypedArray.obtainStyledAttributes(context, attrs,R.styleable.SkinCompatTextHelper, defStyleAttr, 0);final int ap &#61; a.getResourceId(R.styleable.SkinCompatTextHelper_android_textAppearance, INVALID_ID);SkinLog.d(TAG, "ap &#61; " &#43; ap);a.recycle();if (ap !&#61; INVALID_ID) {a &#61; TintTypedArray.obtainStyledAttributes(context, ap, R.styleable.SkinTextAppearance);if (a.hasValue(R.styleable.SkinTextAppearance_android_textColor)) {mTextColorResId &#61; a.getResourceId(R.styleable.SkinTextAppearance_android_textColor, INVALID_ID);SkinLog.d(TAG, "mTextColorResId &#61; " &#43; mTextColorResId);}if (a.hasValue(R.styleable.SkinTextAppearance_android_textColorHint)) {mTextColorHintResId &#61; a.getResourceId(R.styleable.SkinTextAppearance_android_textColorHint, INVALID_ID);SkinLog.d(TAG, "mTextColorHintResId &#61; " &#43; mTextColorHintResId);}a.recycle();}// Now read the style&#39;s valuesa &#61; TintTypedArray.obtainStyledAttributes(context, attrs, R.styleable.SkinTextAppearance,defStyleAttr, 0);if (a.hasValue(R.styleable.SkinTextAppearance_android_textColor)) {mTextColorResId &#61; a.getResourceId(R.styleable.SkinTextAppearance_android_textColor, INVALID_ID);SkinLog.d(TAG, "mTextColorResId &#61; " &#43; mTextColorResId);}if (a.hasValue(R.styleable.SkinTextAppearance_android_textColorHint)) {mTextColorHintResId &#61; a.getResourceId(R.styleable.SkinTextAppearance_android_textColorHint, INVALID_ID);SkinLog.d(TAG, "mTextColorHintResId &#61; " &#43; mTextColorHintResId);}a.recycle();applySkin();}public void onSetTextAppearance(Context context, int resId) {final TintTypedArray a &#61; TintTypedArray.obtainStyledAttributes(context,resId, R.styleable.SkinTextAppearance);if (a.hasValue(R.styleable.SkinTextAppearance_android_textColor)) {mTextColorResId &#61; a.getResourceId(R.styleable.SkinTextAppearance_android_textColor, INVALID_ID);SkinLog.d(TAG, "mTextColorResId &#61; " &#43; mTextColorResId);}if (a.hasValue(R.styleable.SkinTextAppearance_android_textColorHint)) {mTextColorHintResId &#61; a.getResourceId(R.styleable.SkinTextAppearance_android_textColorHint, INVALID_ID);SkinLog.d(TAG, "mTextColorHintResId &#61; " &#43; mTextColorHintResId);}a.recycle();applySkin();}public void applySkin() {mTextColorResId &#61; checkResourceId(mTextColorResId);if (mTextColorResId !&#61; INVALID_ID) {ColorStateList color &#61; SkinCompatResources.getInstance().getColorStateList(mTextColorResId);mView.setTextColor(color);}mTextColorHintResId &#61; checkResourceId(mTextColorHintResId);if (mTextColorHintResId !&#61; INVALID_ID) {ColorStateList color &#61; SkinCompatResources.getInstance().getColorStateList(mTextColorHintResId);mView.setHintTextColor(color);}}
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120

开发过程中遇到的一些问题

在5.0以上, 使用color为ImageView设置src, 可以通过getColorStateList获取资源, 而在5.0以下, 需要通过ColorDrawable setColor的方式实现

String typeName &#61; mView.getResources().getResourceTypeName(mSrcResId);
if ("color".equals(typeName)) {if (Build.VERSION.SDK_INT int color &#61; SkinCompatResources.getInstance().getColor(mSrcResId);Drawable drawable &#61; mView.getDrawable();if (drawable instanceof ColorDrawable) {((ColorDrawable) drawable.mutate()).setColor(color);} else {mView.setImageDrawable(new ColorDrawable(color));}} else {ColorStateList colorStateList &#61; SkinCompatResources.getInstance().getColorStateList(mSrcResId);Drawable drawable &#61; mView.getDrawable();DrawableCompat.setTintList(drawable, colorStateList);mView.setImageDrawable(drawable);}
} else if ("drawable".equals(typeName)) {Drawable drawable &#61; SkinCompatResources.getInstance().getDrawable(mSrcResId);mView.setImageDrawable(drawable);
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

还有很多问题, 有兴趣的同学可以来一起交流解决.


总结


  1. 这样的做法与网上其他框架相比优势在哪里, 为什么重复造轮子

    • 在增加框架开发成本的基础上降低了框架使用的成本, 我觉得更有意义, 一次开发, 所有Android 开发者都受用;
    • 换肤框架对业务代码的侵入性比较小, 业务代码只需要继承自SkinCompatActivity, 不需要实现接口重写方法, 不需要其他额外的代码, 接入方便, 假如将来不想再使用本框架, 只需要把SkinCompatActivity改为原生Activity即可;
    • 深入源码, 和源码实现方式类似, 兼容性更好.
  2. 为什么选择继承自AppCompatActivity, AppCompatTextView…而不是选择直接继承自Activity, TextView…

    • 本身appcompat-v7包是一个support包, 兼容原生控件, 同时符合Material design, 我们只需要获取我们想要换肤的属性就可以在不破坏support包属性的前提下进行换肤;
    • 参与开发的同学更多的话, 完全可以支持一套继承自Activity, TextView…的skin support包.
  3. 自定义View能否支持, 第三方控件是否支持换肤

    • 答案是肯定的, 完全可以参照SkinCompatTextView的实现, 自己去实现自定义控件, 对于使用者来说, 扩展性很好.

源码地址: https://github.com/ximsfei/Android-skin-support


推荐阅读
  • 1简介本文结合数字信号处理课程和Matlab程序设计课程的相关知识,给出了基于Matlab的音乐播放器的总体设计方案,介绍了播放器主要模块的功能,设计与实现方法.我们将该设 ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 标题: ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • 欢乐的票圈重构之旅——RecyclerView的头尾布局增加
    项目重构的Git地址:https:github.comrazerdpFriendCircletreemain-dev项目同步更新的文集:http:www.jianshu.comno ... [详细]
  • 本文介绍了在MFC下利用C++和MFC的特性动态创建窗口的方法,包括继承现有的MFC类并加以改造、插入工具栏和状态栏对象的声明等。同时还提到了窗口销毁的处理方法。本文详细介绍了实现方法并给出了相关注意事项。 ... [详细]
  • Netty源代码分析服务器端启动ServerBootstrap初始化
    本文主要分析了Netty源代码中服务器端启动的过程,包括ServerBootstrap的初始化和相关参数的设置。通过分析NioEventLoopGroup、NioServerSocketChannel、ChannelOption.SO_BACKLOG等关键组件和选项的作用,深入理解Netty服务器端的启动过程。同时,还介绍了LoggingHandler的作用和使用方法,帮助读者更好地理解Netty源代码。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • PHP中的单例模式与静态变量的区别及使用方法
    本文介绍了PHP中的单例模式与静态变量的区别及使用方法。在PHP中,静态变量的存活周期仅仅是每次PHP的会话周期,与Java、C++不同。静态变量在PHP中的作用域仅限于当前文件内,在函数或类中可以传递变量。本文还通过示例代码解释了静态变量在函数和类中的使用方法,并说明了静态变量的生命周期与结构体的生命周期相关联。同时,本文还介绍了静态变量在类中的使用方法,并通过示例代码展示了如何在类中使用静态变量。 ... [详细]
  • Oracle seg,V$TEMPSEG_USAGE与Oracle排序的关系及使用方法
    本文介绍了Oracle seg,V$TEMPSEG_USAGE与Oracle排序之间的关系,V$TEMPSEG_USAGE是V_$SORT_USAGE的同义词,通过查询dba_objects和dba_synonyms视图可以了解到它们的详细信息。同时,还探讨了V$TEMPSEG_USAGE的使用方法。 ... [详细]
  • 本文介绍了在iOS开发中使用UITextField实现字符限制的方法,包括利用代理方法和使用BNTextField-Limit库的实现策略。通过这些方法,开发者可以方便地限制UITextField的字符个数和输入规则。 ... [详细]
  • Activiti7流程定义开发笔记
    本文介绍了Activiti7流程定义的开发笔记,包括流程定义的概念、使用activiti-explorer和activiti-eclipse-designer进行建模的方式,以及生成流程图的方法。还介绍了流程定义部署的概念和步骤,包括将bpmn和png文件添加部署到activiti数据库中的方法,以及使用ZIP包进行部署的方式。同时还提到了activiti.cfg.xml文件的作用。 ... [详细]
  • 本文总结了在编写JS代码时,不同浏览器间的兼容性差异,并提供了相应的解决方法。其中包括阻止默认事件的代码示例和猎取兄弟节点的函数。这些方法可以帮助开发者在不同浏览器上实现一致的功能。 ... [详细]
author-avatar
家居生活我最大_386
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有