目录介绍

  • 01.遇到的实际需求分析
  • 02.原生TabLayout局限
  • 03.TabLayout源码解析
    • 3.1 Tab选项卡如何实现
    • 3.2 滑动切换Tab选项卡
    • 3.3 Tab选项卡指示线宽度
  • 04.设置自定义tabView选项卡
  • 05.自定义指示器的长度
  • 06.设置滑动改变选项卡颜色
  • 07.使用反射的注意要点
  • 08.混淆时用到反射注意项

好消息

  • 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计N篇[近100万字,陆续搬到网上],转载请注明出处,谢谢!
  • 链接地址:https://github.com/yangchong211/YCBlogs
  • 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!

01.遇到的实际需求分析

  • 实际开发中UI的效果图

    • 一般要求文字内容和指示线的宽度要一样
  • 使用TabLayout的效果图
    • 一般指示线的宽度要大于文字内容
  • 遇到问题分析
    • 设置tabPaddingStart和tabPaddingEnd,但是布局填上去后发现并没有用。
  • 实现方案
    • 第一种:自定义类似TabLayout的控件,代码量巨大,且GitHub上有许多已经比较成熟的库,代码质量是层次不齐。
    • 第二种:在原有基础上通过继承TabLayout控件,重写其中几个方法,并且通过反射来修改部分属性,也能达到第一种方案效果。
    • 下面就来讲一下我自己通过第二种方案实现步骤和原理!
  • 最终UI效果图展示

02.原生TabLayout局限

  • 一张图看懂TabLayout的结构

    • 如果要用代码进行表示的话,大概是这样的。TabLayout继承自HorizontalScrollView,而都知道ScrollView只能添加一个子 View,所以SlidingTabIndicator就是那个用来添加子View 的横向LinearLayout。
    //28版本代码
    public class TabLayout extends HorizontalScrollView { private class SlidingTabIndicator extends LinearLayout { }
    }
  • 存在的局限性
    • 第一个无法改变指示线的宽度
    • 第二个无法做到滑动改变tab选项卡颜色渐变的效果【有的还需要放大效果】

03.TabLayout源码解析

3.1 Tab选项卡如何实现

  • 第一种方式,直接通过addTab方法添加tab选项卡,代码如下所示

    TabLayout.Tab tab = tabLayout.newTab();
    View tabView = new TextView(this);
    tabLayout.setCustomView(tabView);
    tabLayout.addTab(tab);
  • 第二种方式,通过设置FragmentPagerAdapter中的getPageTitle也可以添加tab选项卡,代码如下所示
    mTitleList.add("潇湘剑雨");
    FragmentManager supportFragmentManager = getSupportFragmentManager();
    PagerAdapter myAdapter = new PagerAdapter(supportFragmentManager, mFragments, mTitleList);
    tabLayout.setAdapter(myAdapter); public class PagerAdapter extends FragmentPagerAdapter { private List<?> mFragment;
    private List<String> mTitleList; public PagerAdapter(FragmentManager fm, List<?> mFragment, List<String> mTitleList) {
    super(fm);
    this.mFragment = mFragment;
    this.mTitleList = mTitleList;
    } @Override
    public CharSequence getPageTitle(int position) {
    if (mTitleList != null) {
    return mTitleList.get(position);
    } else {
    return "";
    }
    }
    }
    • 接下来看一下tabLayout源码是如何拿到getPageTitle方法的内容而达到设置addTab的目的。主要看源码中的populateFromPagerAdapter方法。看到下面代码是不是豁然开朗了……
    void populateFromPagerAdapter() {
    this.removeAllTabs();
    if (this.pagerAdapter != null) {
    int adapterCount = this.pagerAdapter.getCount(); int curItem;
    for(curItem = 0; curItem < adapterCount; ++curItem) {
    this.addTab(this.newTab().setText(this.pagerAdapter.getPageTitle(curItem)), false);
    } if (this.viewPager != null && adapterCount > 0) {
    curItem = this.viewPager.getCurrentItem();
    if (curItem != this.getSelectedTabPosition() && curItem < this.getTabCount()) {
    this.selectTab(this.getTabAt(curItem));
    }
    }
    }
    }
  • 不管是上面那种方式,那么如何将tab添加到SlidingTabIndicator布局中呢?
    • 通过下面代码可以看到,最终是通过slidingTabIndicator对象调用addView将tabView添加到SlidingTabIndicator布局之中的。
    public void addTab(@NonNull TabLayout.Tab tab, int position, boolean setSelected) {
    if (tab.parent != this) {
    throw new IllegalArgumentException("Tab belongs to a different TabLayout.");
    } else {
    this.configureTab(tab, position);
    this.addTabView(tab);
    if (setSelected) {
    tab.select();
    }
    }
    } private void addTabView(TabLayout.Tab tab) {
    TabLayout.TabView tabView = tab.view;
    this.slidingTabIndicator.addView(tabView, tab.getPosition(), this.createLayoutParamsForTabs());
    }
  • 为什么要分析这个addTab?
    • 因为需求说了,需要在滑动的时候,随着滑动而改变tabView的文字颜色,这一点原生TabLayout并没有实现。所以要实现这个逻辑,就必须重写TabLayout的addTab方法,然后将自己自定义的tabView添加到tab中,这个下面会讲如何实现……

3.2 滑动切换Tab选项卡

  • 第一步:随着页面的滑动文字颜色渐变那么肯定少不了ViewPager的页面监听,这个在我们调用setupWithViewPager的时候TabLayout就已经添加监听。那么先来看下源码监听滑动是如何实现的?

    • 绑定 ViewPager 只需要一行代码mTabLayout.setupWithViewPager(mViewPager)即可。
    • 可以看到当viewPager不为null的时候,先移除listener监听事件。然后在创建listener监听,并且重置状态。
    private void setupWithViewPager(@Nullable ViewPager viewPager, boolean autoRefresh, boolean implicitSetup) {
    if (this.viewPager != null) {
    if (this.pageChangeListener != null) {
    this.viewPager.removeOnPageChangeListener(this.pageChangeListener);
    } if (this.adapterChangeListener != null) {
    this.viewPager.removeOnAdapterChangeListener(this.adapterChangeListener);
    }
    } if (this.currentVpSelectedListener != null) {
    this.removeOnTabSelectedListener(this.currentVpSelectedListener);
    this.currentVpSelectedListener = null;
    } if (viewPager != null) {
    this.viewPager = viewPager;
    if (this.pageChangeListener == null) {
    this.pageChangeListener = new TabLayout.TabLayoutOnPageChangeListener(this);
    } this.pageChangeListener.reset();
    viewPager.addOnPageChangeListener(this.pageChangeListener);
    this.currentVpSelectedListener = new TabLayout.ViewPagerOnTabSelectedListener(viewPager);
    this.addOnTabSelectedListener(this.currentVpSelectedListener);
    PagerAdapter adapter = viewPager.getAdapter();
    if (adapter != null) {
    this.setPagerAdapter(adapter, autoRefresh);
    } if (this.adapterChangeListener == null) {
    this.adapterChangeListener = new TabLayout.AdapterChangeListener();
    } this.adapterChangeListener.setAutoRefresh(autoRefresh);
    viewPager.addOnAdapterChangeListener(this.adapterChangeListener);
    this.setScrollPosition(viewPager.getCurrentItem(), 0.0F, true);
    } else {
    this.viewPager = null;
    this.setPagerAdapter((PagerAdapter)null, false);
    } this.setupViewPagerImplicitly = implicitSetup;
    }
  • 那么滑动是如何切换选项卡和指示线呢,具体看一下TabLayoutOnPageChangeListener滑动监听源码。
    • 主要是看onPageSelected方法,该方法是通过tabLayout.selectTab来切换选项卡的。
    public static class TabLayoutOnPageChangeListener implements OnPageChangeListener {
    
        public TabLayoutOnPageChangeListener(TabLayout tabLayout) {
    this.tabLayoutRef = new WeakReference(tabLayout);
    } public void onPageScrollStateChanged(int state) {
    this.previousScrollState = this.scrollState;
    this.scrollState = state;
    } public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    TabLayout tabLayout = (TabLayout)this.tabLayoutRef.get();
    if (tabLayout != null) {
    boolean updateText = this.scrollState != 2 || this.previousScrollState == 1;
    boolean updateIndicator = this.scrollState != 2 || this.previousScrollState != 0;
    tabLayout.setScrollPosition(position, positionOffset, updateText, updateIndicator);
    } } public void onPageSelected(int position) {
    TabLayout tabLayout = (TabLayout)this.tabLayoutRef.get();
    if (tabLayout != null && tabLayout.getSelectedTabPosition() != position && position < tabLayout.getTabCount()) {
    boolean updateIndicator = this.scrollState == 0 || this.scrollState == 2 && this.previousScrollState == 0;
    tabLayout.selectTab(tabLayout.getTabAt(position), updateIndicator);
    }
    }
    }
  • 知道了滑动切换选项卡后,就思考一下,能否通过反射来使用自己的滑动监听事件,然后在onPageSelected方法中,滑动改变选项卡中文字的颜色,或者缩放的功能呢。答案是可以的。

3.3 Tab选项卡指示线宽度

  • 具体可以看updateIndicatorPosition源码

    • 可以看到先获取当前滑动位置的tabView,如果内容不为空,则获取左右的位置。
    • 在滑块滑动的时候,如果滑动超过了上一个或是下一个滑块一半的话。那就说明移动到了上一个或是下一个滑块,然后取出left和right
    • 最后设置滑块的位置
    private void updateIndicatorPosition() {
    //根据当前滑块的位置拿到当前TabView
    View selectedTitle = this.getChildAt(this.selectedPosition);
    int left;
    int right;
    if (selectedTitle != null && selectedTitle.getWidth() > 0) {
    //拿到TabView的左、右位置
    left = selectedTitle.getLeft();
    right = selectedTitle.getRight();
    if (!TabLayout.this.tabIndicatorFullWidth && selectedTitle instanceof TabLayout.TabView) {
    this.calculateTabViewContentBounds((TabLayout.TabView)selectedTitle, TabLayout.this.tabViewContentBounds);
    left = (int)TabLayout.this.tabViewContentBounds.left;
    right = (int)TabLayout.this.tabViewContentBounds.right;
    } //在滑块滑动的时候,如果滑动超过了上一个或是下一个滑块一半的话
    //那就说明移动到了上一个或是下一个滑块,然后取出left和right
    if (this.selectionOffset > 0.0F && this.selectedPosition < this.getChildCount() - 1) {
    View nextTitle = this.getChildAt(this.selectedPosition + 1);
    int nextTitleLeft = nextTitle.getLeft();
    int nextTitleRight = nextTitle.getRight();
    if (!TabLayout.this.tabIndicatorFullWidth && nextTitle instanceof TabLayout.TabView) {
    this.calculateTabViewContentBounds((TabLayout.TabView)nextTitle, TabLayout.this.tabViewContentBounds);
    nextTitleLeft = (int)TabLayout.this.tabViewContentBounds.left;
    nextTitleRight = (int)TabLayout.this.tabViewContentBounds.right;
    } left = (int)(this.selectionOffset * (float)nextTitleLeft + (1.0F - this.selectionOffset) * (float)left);
    right = (int)(this.selectionOffset * (float)nextTitleRight + (1.0F - this.selectionOffset) * (float)right);
    }
    } else {
    right = -1;
    left = -1;
    }
    //设置滑块的位置
    this.setIndicatorPosition(left, right);
    }
  • 然后看一下setIndicatorPosition的代码
    • 设置滑块的宽度是根据子TabView的宽度来设置的,也就是说,TabView的宽度是多少,那么滑块的宽度就是多少。
    void setIndicatorPosition(int left, int right) {
    if (left != this.indicatorLeft || right != this.indicatorRight) {
    this.indicatorLeft = left;
    this.indicatorRight = right;
    ViewCompat.postInvalidateOnAnimation(this);
    }
    }
  • 为何要分析这个?
    • 因为如果你要改变指示器的宽度,那么必须要能够动态改变左右的位置。知道了这个大概的原理,那么下面利用反射设置选项卡左右的间距来改变指示器的长度就知道怎么实现呢。

04.实现滑动改变颜色

  • 滑动改变指示器文字变色

    • TabLayout中可以设置文字内容,通过上面3.2源码分析,可以知道通过addTab添加自定义选项卡,那么滑动改变选项卡tabView的颜色,可以会涉及到监听滑动。因此这里需要用反射替换成自己的滑动监听,然后在TabLayoutOnPageChangeListener的监听类中的onPageScrolled方法,改变tabView的颜色。
  • 通过反射找到源码中pageChangeListener成员变量,然后设置暴力访问权限。
    • 然后获取TabLayoutOnPageChangeListener的对象,删除自带的监听,同时将自己自定义的滑动监听listener添加上。
    @Override
    public void setupWithViewPager(@Nullable ViewPager viewPager, boolean autoRefresh) {
    super.setupWithViewPager(viewPager, autoRefresh);
    try {
    //通过反射找到mPageChangeListener
    Field field = getPageChangeListener();
    field.setAccessible(true);
    TabLayoutOnPageChangeListener listener = (TabLayoutOnPageChangeListener) field.get(this);
    if (listener!=null && viewPager!=null) {
    //删除自带监听
    viewPager.removeOnPageChangeListener(listener);
    OnPageChangeListener mPageChangeListener = new OnPageChangeListener(this);
    mPageChangeListener.reset();
    viewPager.addOnPageChangeListener(mPageChangeListener);
    }
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    • 然后看一下反射的代码,我在网上看到好多博客,没有区分27前和28后的问题。这个地方一定要注意一下!
    /**
    * 反射获取私有的mPageChangeListener属性,考虑support 28以后变量名修改的问题
    * @return Field
    * @throws NoSuchFieldException
    */
    private Field getPageChangeListener() throws NoSuchFieldException {
    Class clazz = TabLayout.class;
    try {
    // support design 27及一下版本
    return clazz.getDeclaredField("mPageChangeListener");
    } catch (NoSuchFieldException e) {
    e.printStackTrace();
    // 可能是28及以上版本
    return clazz.getDeclaredField("pageChangeListener");
    }
    }
  • 然后看一下自定义的OnPageChangeListener
    • 采用弱引用方式防止监听listener内存泄漏,算是一个小的优化
    /**
    * 滑动监听,核心逻辑
    * 建议如果是activity退到后台,或者关闭页面,将listener给remove掉
    * 采用弱引用方式防止监听listener内存泄漏,算是一个小的优化
    */
    private static class OnPageChangeListener extends TabLayoutOnPageChangeListener { private final WeakReference<CustomTabLayout> mTabLayoutRef;
    private int mPreviousScrollState;
    private int mScrollState; OnPageChangeListener(TabLayout tabLayout) {
    super(tabLayout);
    mTabLayoutRef = new WeakReference<>((CustomTabLayout) tabLayout);
    } /**
    * 这个方法是滚动状态发生变化是调用
    * @param state 桩体
    */
    @Override
    public void onPageScrollStateChanged(final int state) {
    mPreviousScrollState = mScrollState;
    mScrollState = state;
    } /**
    * 正在滚动时调用
    * @param position 索引
    * @param positionOffset offset偏移
    * @param positionOffsetPixels offsetPixels
    */
    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    super.onPageScrolled(position, positionOffset, positionOffsetPixels);
    CustomTabLayout tabLayout = mTabLayoutRef.get();
    if (tabLayout == null) {
    return;
    }
    final boolean updateText = mScrollState != SCROLL_STATE_SETTLING ||
    mPreviousScrollState == SCROLL_STATE_DRAGGING;
    if (updateText) {
    tabLayout.tabScrolled(position, positionOffset);
    }
    } /**
    * 选中时调用
    * @param position 索引
    */
    @Override
    public void onPageSelected(int position) {
    super.onPageSelected(position);
    CustomTabLayout tabLayout = mTabLayoutRef.get();
    mPreviousScrollState = SCROLL_STATE_SETTLING;
    tabLayout.setSelectedView(position);
    } /**
    * 重置状态
    */
    void reset() {
    mPreviousScrollState = mScrollState = SCROLL_STATE_IDLE;
    }
    }

05.自定义指示器的长度

  • 通过反射的方式修改指示器长度,如果需要指示器宽度等于文字宽度需要自己微调,或者28版本直接通过设置app:tabIndicatorFullWidth="false"属性即可让内容和指示器宽度一样。

    • 原理就是通过反射的方式获取TabLayout的字段mTabStrip(27之前)或者slidingTabIndicator(28之后),然后再去遍历修改每一个子 View 的 Margin 值。代码如下:
    /**
    * 通过反射设置TabLayout每一个的长度
    * @param left 左边 Margin 单位 dp
    * @param right 右边 Margin 单位 dp
    */
    public void setIndicator(int left, int right) {
    Field tabStrip = null;
    try {
    tabStrip = getTabStrip();
    tabStrip.setAccessible(true);
    } catch (NoSuchFieldException e) {
    e.printStackTrace();
    } LinearLayout llTab = null;
    try {
    if (tabStrip != null) {
    llTab = (LinearLayout) tabStrip.get(this);
    }
    } catch (Exception e) {
    e.printStackTrace();
    } int l = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, left,
    Resources.getSystem().getDisplayMetrics());
    int r = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, right,
    Resources.getSystem().getDisplayMetrics()); if (llTab != null) {
    for (int i = 0; i < llTab.getChildCount(); i++) {
    View child = llTab.getChildAt(i);
    child.setPadding(0, 0, 0, 0);
    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
    0, LinearLayout.LayoutParams.MATCH_PARENT, 1);
    params.leftMargin = l;
    params.rightMargin = r;
    child.setLayoutParams(params);
    child.invalidate();
    }
    }
    }
  • 然后看一下反射获取tabStrip的代码
    /**
    * 反射获取私有的mTabStrip属性,考虑support 28以后变量名修改的问题
    * @return Field
    * @throws NoSuchFieldException
    */
    private Field getTabStrip() throws NoSuchFieldException {
    Class clazz = TabLayout.class;
    try {
    // support design 27及一下版本
    return clazz.getDeclaredField("mTabStrip");
    } catch (NoSuchFieldException e) {
    e.printStackTrace();
    // 可能是28及以上版本
    return clazz.getDeclaredField("slidingTabIndicator");
    }
    }
  • 这里其实也可以不用反射,那么该怎么实现呢?
    • 需要注意一点,需要在Tablayout设置完成后操作,并且必须等所有绘制操作结束,使用tabLayout.post拿到属性参数,然后设置下margin。
    public void setTabWidth(TabLayout tabLayout){
    //拿到slidingTabIndicator的布局
    LinearLayout mTabStrip = (LinearLayout) tabLayout.getChildAt(0);
    //遍历SlidingTabStrip的所有TabView子view
    for (int i = 0; i < mTabStrip.getChildCount(); i++) {
    View tabView = mTabStrip.getChildAt(i);
    LinearLayout.LayoutParams params = (LinearLayout.LayoutParams)tabView.getLayoutParams();
    //给TabView设置leftMargin和rightMargin
    params.leftMargin = dp2px(10);
    params.rightMargin = dp2px(10);
    tabView.setLayoutParams(params);
    //触发绘制
    tabView.invalidate();
    }
    }

06.设置滑动改变选项卡颜色

  • 滑动时如何改变选项卡的颜色呢?当然在滚动的时候去动态改变属性,具体的做法:
  • 在TabLayoutOnPageChangeListener中监听,主要看onPageScrolled方法
    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    super.onPageScrolled(position, positionOffset, positionOffsetPixels);
    CustomTabLayout tabLayout = mTabLayoutRef.get();
    if (tabLayout == null) {
    return;
    }
    final boolean updateText = mScrollState != SCROLL_STATE_SETTLING ||
    mPreviousScrollState == SCROLL_STATE_DRAGGING;
    if (updateText) {
    tabLayout.tabScrolled(position, positionOffset);
    }
    }
  • 然后看一下tabScrolled方法,代码如下所示
    • 这个方法里,主要是拿到当前tabView和下一个tabView,然后依次改变Progress进度,以此达到更改文字的颜色。
    /**
    * 滑动改变自定义tabView的颜色
    * @param position 索引
    * @param positionOffset 偏移量
    */
    private void tabScrolled(int position, float positionOffset) {
    if (positionOffset == 0.0F) {
    return;
    }
    //当前tabView
    CustomTabView currentTrackView = getCustomTabView(position);
    //下一个tabView
    CustomTabView nextTrackView = getCustomTabView(position + 1);
    if (currentTrackView != null) {
    currentTrackView.setDirection(1);
    currentTrackView.setProgress(1.0F - positionOffset);
    }
    if (nextTrackView != null) {
    nextTrackView.setDirection(0);
    nextTrackView.setProgress(positionOffset);
    }
    }
  • 然后在CustomTabView中,看代码如下所示
    • 调用invalidate()方法会调用onDraw()方法,然后去达到重绘view的目的。
    public void setProgress(float progress) {
    this.mProgress = progress;
    invalidate();
    }
  • 接着看看onDraw这个方法做了什么操作
    @Override
    protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    if (mDirection == DIRECTION_LEFT) {
    drawChangeLeft(canvas);
    drawOriginLeft(canvas);
    } else if (mDirection == DIRECTION_RIGHT) {
    drawOriginRight(canvas);
    drawChangeRight(canvas);
    } else if (mDirection == DIRECTION_TOP) {
    drawOriginTop(canvas);
    drawChangeTop(canvas);
    } else if (mDirection == DIRECTION_BOTTOM){
    drawOriginBottom(canvas);
    drawChangeBottom(canvas);
    }
    }
  • 然后看其中的一个drawChangeLeft方法
    private void drawChangeLeft(Canvas canvas) {
    drawTextHor(canvas, mTextChangeColor, mTextStartX, (int) (mTextStartX + mProgress * mTextWidth));
    } /**
    * 横向
    * @param canvas 画板
    * @param color 颜色
    * @param startX 开始x
    * @param endX 结束x
    */
    private void drawTextHor(Canvas canvas, int color, int startX, int endX) {
    mPaint.setColor(color);
    if (debug) {
    mPaint.setStyle(Style.STROKE);
    canvas.drawRect(startX, 0, endX, getMeasuredHeight(), mPaint);
    }
    canvas.save();
    canvas.clipRect(startX, 0, endX, getMeasuredHeight());
    // right, bottom
    canvas.drawText(mText, mTextStartX, getMeasuredHeight() / 2
    - ((mPaint.descent() + mPaint.ascent()) / 2), mPaint);
    canvas.restore();
    }

07.使用反射的注意要点

  • 比如或者mTabStrip属性,网上许多没有区分27和28名称的变化。如果因为名称的问题,会导致反射获取不到Field,那么所做的操作也就失效了,这是一个很大的风险。

    /**
    * 反射获取私有的mTabStrip属性,考虑support 28以后变量名修改的问题
    * @return Field
    * @throws NoSuchFieldException
    */
    private Field getTabStrip() throws NoSuchFieldException {
    Class clazz = TabLayout.class;
    try {
    // support design 27及一下版本
    return clazz.getDeclaredField("mTabStrip");
    } catch (NoSuchFieldException e) {
    e.printStackTrace();
    // 可能是28及以上版本
    return clazz.getDeclaredField("slidingTabIndicator");
    }
    }

08.混淆时用到反射注意项

  • 还有一点就是有的人这么使用会报错,是因为混淆产生的问题,反射slidingTabIndicator或者pageChangeListener的时候可能会出问题,可以在混淆配置里面设置下TabLayout不被混淆。

    -keep class android.support.design.widget.TabLayout{*;}

其他介绍

01.关于博客汇总链接

02.关于我的博客

博客汇总项目开源地址:https://github.com/yangchong211/YCBlogs

TabLayout项目开源地址:https://github.com/yangchong211/YcTabLyout

反射改变TabLayout属性的更多相关文章

  1. String 类型的值能够被反射改变从而引发的意外事件

    今天刷技术文章,遇到了一个问题,用 Java 反射机制去修改 String 变量的值,出于深入研究,就发现了一个问题,即,用初始值比较修改后的值,用 == or .equals() 方法,出现了相等的 ...

  2. 【用户交互】APP没有退出前台但改变系统属性如何实时更新UI?监听系统广播,让用户交互更舒心~

    前日,一小伙伴问我一个问题,说它解决了半天都没解决这个问题,截图如下: 大概楼主理解如下: 如果在应用中有一个判断wifi的开关和一个当前音量大小的seekbar以及一个获取当前电量多少的按钮,想知道 ...

  3. jqGrid使用setColProp方法动态改变列属性

    在使用jqGrid插件时,有时我们需要动态改变列的属性,可使用setColProp方法,用法如下 jQuery(”#grid_id”).setColProp('colname',{editoption ...

  4. jquery获取、改变元素属性值

    //标签的属性称作元素属性,在JS里对应的DOM对象的对应属性叫DOM属性.JS里的DOM属性名有时和原元素属性名不同. //==================================操作元 ...

  5. c# 如何通过反射 获取\设置属性值

    c# 如何通过反射 获取\设置属性值 //定义类public class MyClass{public int Property1 { get; set; }}static void Main(){M ...

  6. Java循环一个对象的所有属性,并通过反射给这些属性赋值/取值

    Java循环一个对象的所有属性,并通过反射给这些属性赋值/取值 说到循环遍历,最常见的遍历数组/列表.Map等.但是,在开发过程中,有时需要循环遍历一个对象的所有属性.遍历对象的属性该如何遍历呢?查了 ...

  7. selenium用jquery改变元素属性

    一.jQuery 语法 jQuery 语法是通过选取 HTML 元素,并对选取的元素执行某些操作. 1.基础语法: $(selector).action() 选择符(selector)即," ...

  8. SpringBoot系列四:SpringBoot开发(改变环境属性、读取资源文件、Bean 配置、模版渲染、profile 配置)

    声明:本文来源于MLDN培训视频的课堂笔记,写在这里只是为了方便查阅. 1.概念 SpringBoot 开发深入 2.具体内容 在之前已经基本上了解了整个 SpringBoot 运行机制,但是也需要清 ...

  9. python 四种方法修改类变量,实例对象调用类方法改变类属性的值,类对象调用类方法改变类属性的值,调用实例方法改变类属性的值,直接修改类属性的值

    三种方法修改类变量,实例对象调用类方法改变类属性的值,类对象调用类方法改变类属性的值,调用实例方法改变类属性的值,类名就是类对象,city就是类变量, #coding=utf-8 class empl ...

  10. java使用反射给对象属性赋值的两种方法

    java反射无所不能,辣么,怎么通过反射设置一个属性的值呢? 主程序: /** * @author tengqingya * @create 2017-03-05 15:54 */ public cl ...

随机推荐

  1. Python-open函数-读写文件

    一.open 函数语法 open() 函数的作用是打开一个文件,并返回一个 file对象(即文件对象). open 是一个动作,可以理解为我们打开文档的点击动作. file 对象是一个实物,可以理解为 ...

  2. NC15532 Happy Running

    题目链接 题目 题目描述 Happy Running, an application for runners, is very popular in CHD. In order to lose wei ...

  3. NC24263 USACO 2018 Feb G]Directory Traversal

    题目链接 题目 题目描述 奶牛Bessie令人惊讶地精通计算机.她在牛棚的电脑里用一组文件夹储存了她所有珍贵的文件,比如: bessie/ folder1/ file1 folder2/ file2 ...

  4. Linux dmesg命令使用方法详解

    一.命令简介  dmesg(display message)命令用于显示开机信息.kernel 会将开机信息存储在 ring buffer 中.您若是开机时来不及查看信息,可利用 dmesg 来查看. ...

  5. Java 23种设计模式的分类和使用场景

    听说过GoF吧? GoF是设计模式的经典名著Design Patterns: Elements of Reusable Object-Oriented Software(中译本名为<设计模式-- ...

  6. win32 - 匿名管道的使用

    目标: 创建一个父进程和子进程,在子进程的控制台窗口输入数据,数据通过管道发送给父进程,父进程的控制台窗口读取数据,最后将数据打印出来. Parent.cpp //CMD.exe #include & ...

  7. win32 - 使用LookupAccountName查找SID

    可以使用LookupAccountNameA获取sid. LookupAccountName函数接受系统名称和帐户作为输入.它检索该帐户的安全标识符(SID)以及在其上找到该帐户的域的名称. 使用此a ...

  8. Web流式下载数据时展示提示信息

    以Web方式下载数据有多种场景: 1.服务端本身已经存在文件,此时只需要一个文件访问地址即可下载,比如:将文件URL设置为<a>标签的href属性即可,点击<a>标签就能立即触 ...

  9. OpenCV开发笔记(五十七):红胖子8分钟带你深入了解直方图反向投影(图文并茂+浅显易懂+程序源码)

    若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...

  10. django中信号

    # 信号的理解 在某个行为进行的某个阶段给这个行为添加一个附带的行为 # 相关api ## 数据表 pre_init # django的model执行其构造方法前,自动触发 post_init # d ...