目录介绍

  • 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. Shell中调用可执行文件,手动执行可以执行,crontab执行就报错:exec: java: not found

    今天发现一个很奇怪的问题,就是我编写的shell脚本, 手动执行可以正常执行,但是放到crontab中就报错.line 60: exec: java: not  found 百度搜索发现原来是java ...

  2. Linux 在线安装MySQL8.0

    1.更新Linux yum yum update 2.安装wget工具(如果已经安装wget,可以跳过该步骤) yum install wget 3.使用wget下载MySQL Yum Reposit ...

  3. Spring Boot + MyBatis-Plus 实现 MySQL 主从复制动态数据源切换

    MySQL 主从复制是一种常见的数据库架构,它可以提高数据库的性能和可用性.动态数据源切换则可以根据业务需求,在不同场景下使用不同的数据源,比如在读多写少的场景下,可以通过切换到从库来分担主库的压力. ...

  4. Windows OhmGraphite 配置

    Windows OhmGraphite 配置 由于windows_exporter无法监控温度相关的指标,那么就需要使用OhmGraphite进行监控该指标. 下载 访问 https://github ...

  5. NC24727 [USACO 2010 Feb G]Slowing down

    题目链接 题目 题目描述 Every day each of Farmer John's N (1 <= N <= 100,000) cows conveniently numbered ...

  6. NC204871 求和

    题目链接 题目 题目描述 已知有 \(n\) 个节点,有 \(n-1\) 条边,形成一个树的结构. 给定一个根节点 \(k\) ,每个节点都有一个权值,节点i的权值为 \(v_i\) . 给 \(m\ ...

  7. Wireguard笔记(一) 节点安装配置和参数说明

    目录 Wireguard笔记(一) 节点安装配置和参数说明 Wireguard笔记(二) 命令行操作 Wireguard笔记(三) lan-to-lan子网穿透和多网段并存 简介 虚拟子网软件,类似于 ...

  8. 升级 vcpkg 遇到的一些坑

    项目上有个需求要用到 wil 库,于是打开 cmd 输入: vcpkg install wil:x86-windows-static 等了很久,一直卡在配置命令 连续试了好几遍,还是不行,安装其他的静 ...

  9. 揭秘一线大厂Redis面试高频考点(3万字长文、吐血整理)

    ## # 3万+长文揭秘一线大厂Redis面试高频考点,整理不易,求一键三连:点赞.分享.收藏 本文,已收录于,我的技术网站 aijiangsir.com,有大厂完整面经,工作技术,架构师成长之路,等 ...

  10. Dubbo使用APISIX作为网关

    为什么使用网关 Dubbo服务本身没有暴露HTTP接口,客户端(如:Web,APP)无法直接调用其提供的方法. 而APISIX可以通过dubbo-proxy插件为Dubbo服务提供外部访问的HTTP接 ...