美文网首页Android 进阶之旅
Android 进阶学习(三十四) Android 中一些常用的

Android 进阶学习(三十四) Android 中一些常用的

作者: Tsm_2020 | 来源:发表于2022-10-10 18:07 被阅读0次

    这里总结的都是平时一些常用的功能,使用系统提供的方法能更快的实现想要的效果

    1.圆形裁剪 outlineProvider

    动画.gif

    想要实现圆形裁剪特别简单,我自己写了一个拓展方法,

    fun View.zhomeOutLineProvider(block: (View?,Outline?) -> Unit){
      this.outlineProvider=object :ViewOutlineProvider(){
          override fun getOutline(view: View?, outline: Outline?) {
              block(view,outline)
          }
      }
      // 使用设定的边距
      this.clipToOutline=true
    }
    

    使用这个拓展方法的情况,指定outline 为圆形就可以了,宽度为view 的宽,高度为View 的高度

      iv_zhome_out_line_provider.zhomeOutLineProvider { view, outline ->
              outline?.setOval(0,0,view?.width?:0,view?.height?:0)
          }
    

    View 设置完 ViewOutlineProvider 后, 阴影的范围也被改变了,举个栗子
    View.outlineProvider=object :ViewOutlineProvider(){}
    把一个矩形的View 修改为 一个圆形的View ,但是 clipToOutline 设置的false
    那么只是将阴影的绘制区域给改变了,
    如果 clipToOutline =true 则是将View 裁剪,同时改变阴影的显示

    2.扩充点击范围 从QMUI中学习到这个方法

    在将QMUI 的代码下载下来,研究了一下他们的工具类,找到了这个方法,放出他们的源码 ,

      /**
       * 扩展点击区域的范围
       *
       * @param view       需要扩展的元素,此元素必需要有父级元素
       * @param expendSize 需要扩展的尺寸(以sp为单位的)
       */
      public static void expendTouchArea(final View view, final int expendSize) {
          if (view != null) {
              final View parentView = (View) view.getParent();
    
              parentView.post(new Runnable() {
                  @Override
                  public void run() {
                      Rect rect = new Rect();
                      view.getHitRect(rect); //如果太早执行本函数,会获取rect失败,因为此时UI界面尚未开始绘制,无法获得正确的坐标
                      rect.left -= expendSize;
                      rect.top -= expendSize;
                      rect.right += expendSize;
                      rect.bottom += expendSize;
                      parentView.setTouchDelegate(new TouchDelegate(rect, view));
                  }
              });
          }
    
      }
    

    发现TouchDelegate 可以扩充点击事件,但是他并不能将这个事件拓展到他的父ViewGroup之外,至于这个方法为什么可以用,我又去看了一下View 的onTouchEvent 的,发现了里面的玄机

      public boolean onTouchEvent(MotionEvent event) {
          if (mTouchDelegate != null) {
              if (mTouchDelegate.onTouchEvent(event)) {
                  return true;
              }
          }
          if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
              switch (action) {
                  case MotionEvent.ACTION_UP:
                /// 这里判断的click事件  
          }
    }
    

    如果View 存在mTouchDelegate ,这个事件的响应会在onClick之前, 而且这个范围也是你自己指定的,一个非常nice的方法,受教了

    3.click 优雅防止多次出发事件 从QMUI中学习到这个方法,

    我将这个方法给写成了拓展方法,方便使用,这里贴一下我改后的

    /**
    *  设定时间内,只让第一个事件执行
    */
    fun throttleClick(wait: Long = 200, block: ((View) -> Unit)): View.OnClickListener {
      return View.OnClickListener { v ->
          val current = SystemClock.uptimeMillis()
          val lastClickTime = (v.getTag(R.id.zhome_view_click) as? Long) ?: 0
          if (current - lastClickTime > wait) {
              v.setTag(R.id.zhome_view_click, current)
              block(v)
          }
      }
    }
    
    /**
    * 简化上面的写法
    */
    fun View.click(wait: Long = 200, block: ((View) -> Unit)){
      this.setOnClickListener(throttleClick(wait,block))
    }
    

    他们写的这个非常巧妙,还有另外一个也是非常好用,

    /**
    * 设定时间内,让最后一个事件执行
    */
    fun debounceClick(wait: Long = 200, block: ((View) -> Unit)): View.OnClickListener {
      return View.OnClickListener { v ->
          var action = (v.getTag(R.id.zhome_view_click) as? DebounceAction)
          if(action == null){
              action = DebounceAction(v, block)
              v.setTag(R.id.zhome_view_click, action)
          }else{
              action.block = block
          }
          v.removeCallbacks(action)
          v.postDelayed(action, wait)
      }
    }
    
    class DebounceAction(val view: View,  var block: ((View) -> Unit)): Runnable {
      override fun run() {
          if(view.isAttachedToWindow){
              block(view)
          }
      }
    }
    

    虽然感觉自己写UI相关的东西挺多的,了解的东西也算不少了,但是看了他们的代码后,对自己的提升还是非常大的,
    下面附上他们项目的链接 https://github.com/Tencent/QMUI_Android

    4.clipdrawable 查看progressbar的时候发现一个level属性,查了相关文章后发现超级实用

    前一阵子在开发的过程中遇到这样一个需求


    image.png

    这个seekbar 拖动的部分是一个渐变色,背景是不变的,最开始打算修改seekbar,或者从网上找一些资料,但是效果都不尽如人意,没办法翻了一下progressbar,想从他的源码中发现他是如何实现的这个效果,就找到了clipdrawable

    具体一个具体的clip 文件

    <clip  xmlns:android="http://schemas.android.com/apk/res/android"
              android:clipOrientation="vertical"
              android:gravity="bottom">
              <shape android:shape="rectangle">
                  <corners android:radius="8dp" />
                  <gradient android:startColor="#FFFFE9"  android:endColor="#F4F5F7" android:angle="90"/>
              </shape>
          </clip>
    

    其中 clipOrientation 指定的是水平渲染还是垂直渲染 gravity 指定的是从哪里开始渲染
    剩下的都是draw的属性了,最终实现的效果


    动画.gif

    5.RecyclerView 使用Scroller滚动到指定位置 用的地方很多,这个的是使用动画滚动过去,

    RecyclerView 滚动的指定item,原生的api是只要这个item的在屏幕上就可以了,不管这个item是否在屏幕的一个上面,从网上看了很多例子,大部分都是根据layoutmanager 来判断的,实现起来超级复杂,下面来看一下我这套方案

    class TopSmoothScroller(context: Context?) : LinearSmoothScroller(context) {
      override fun getHorizontalSnapPreference(): Int {
          return SNAP_TO_START //意思是吸附在顶部
      }
    
      override fun getVerticalSnapPreference(): Int {
          return SNAP_TO_START //意思是吸附在顶部
      }
    }
    

    我是重写了一个recyclerview ,把他的smoothScrollToPosition 给替换了,

      @Override
      public void smoothScrollToPosition(int position) {
          if(getContext()!=null){
              LinearSmoothScroller s1  =new TopSmoothScroller(getContext());
              s1.setTargetPosition(position);
              if(getLayoutManager()!=null){
                  getLayoutManager().startSmoothScroll(s1);
              }
          }
      }
    

    方便快捷

    6.colorfliter 修改颜色,不用替换iamge 节省内存 ,titlebar 滑动渐变初使用

    在titlebar 滑动由透明色背景变成白色背景,到达中间时,需要替换返回图片,和右边的按钮,如果使用图片的资源id 来回设置的话,如果图片加载比较多,会发现明显的卡顿,这是内存抖动造成的,如果使用这个方法

    img.setColorFilter(new PorterDuffColorFilter(ContextCompat.getColor(child.getContext(), R.color.color_ct1_85), PorterDuff.Mode.SRC_IN));
    

    想要变回来也特别简单

    img.setColorFilter(null)
    

    其实在做这个功能的时候,我的想法是能不能将这个滑动渐变的过程整体封装起来,只要使用了我写的titlebar,整个滑动渐变的过程对外都是没有感知的,要不然每次都监听ScrollView 只是copy代码都有点麻烦,我就自己写了一个
    SimpleToolBarAlphaBehavior

    public class SimpleToolBarAlphaBehavior<V> extends CoordinatorLayout.Behavior<View> {
    
      private float deltaY;
    
    //    public int textColor;
    
      private SimpleToolbarListeners listeners;
      public boolean blackToolBar = false;
    
      public SimpleToolBarAlphaBehavior(Context context) {
          super();
      }
    
      public SimpleToolBarAlphaBehavior(Context context, AttributeSet attrs) {
          super(context, attrs);
      }
    
      @Override
      public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
          return dependency instanceof RecyclerView || dependency instanceof NestedScrollView || dependency instanceof SmartRefreshLayout || dependency instanceof ViewPager2;
      }
    
    
      public void onConfigChange(){
          deltaY=0;
      }
    
    
      @Override
      public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
    
          if (deltaY == 0) {
              deltaY = dependency.getY();
          }
          float dy =dependency.getY();
    
          float pre = prepareDate(dy);
    
          Log.i("tian.shm","pre:"+pre);
    
          int alpha = (int) ((pre) * 255);
          int color=Color.argb(alpha,255,255,255);///白色
          child.setBackgroundColor(color);
          addListeners(alpha,pre,child);
          return true;
      }
      public void setSimpleToolbarListeners(SimpleToolbarListeners listeners) {
          this.listeners = listeners;
      }
    
      private void addListeners(int alpha,float pre,View child) {
          if(listeners!=null){
              listeners.onAlphaChange(pre);
          }
          if (alpha >= 127) {
              if (!blackToolBar) {
                  blackToolBar = true;
                  if(child instanceof TitleBarView){
                      ((TitleBarView) child).getLeftImageButton().setColorFilter(new PorterDuffColorFilter(ContextCompat.getColor(child.getContext(), R.color.color_ct1_85), PorterDuff.Mode.SRC_IN));
                      if(((TitleBarView) child).getRightImageButton()!=null){
                          ((TitleBarView) child).getRightImageButton().setColorFilter(new PorterDuffColorFilter(ContextCompat.getColor(child.getContext(), R.color.color_ct1_85), PorterDuff.Mode.SRC_IN));
                      }
                      ((TitleBarView) child).getCenterTextView().setTextColor(ContextCompat.getColor(child.getContext(), R.color.color_ct1_85));
    
                  }
                  if (listeners != null) {
                      listeners.onFullColor();
                  }
              }
          } else {
              if (blackToolBar) {
                  blackToolBar = false;
                  ((TitleBarView) child).getLeftImageButton().setColorFilter(null);
                  ((TitleBarView) child).getCenterTextView().setTextColor(Color.WHITE);
                  if(((TitleBarView) child).getRightImageButton()!=null){
                      ((TitleBarView) child).getRightImageButton().setColorFilter(null);
                  }
                  if (listeners != null) {
                      listeners.onTransparent();
                  }
              }
          }
      }
      private float prepareDate(float dy) {
          if(dy==0f){
              return 1;
          }
          dy = dy < 0 ? 0 : dy;
          float pre = 1-(dy / deltaY);
          if (pre > 0.33f) {
              pre = 1;
          } else {
              pre = pre * 3;
          }
          return pre>1?1:pre;
      }
      public static <V extends View> SimpleToolBarAlphaBehavior from(V view) {
          ViewGroup.LayoutParams params = view.getLayoutParams();
          if (!(params instanceof CoordinatorLayout.LayoutParams)) {
              throw new IllegalArgumentException("The view is not a child of CoordinatorLayout");
          } else {
              CoordinatorLayout.Behavior behavior = ((CoordinatorLayout.LayoutParams) params).getBehavior();
              if (!(behavior instanceof SimpleToolBarAlphaBehavior)) {
                  throw new IllegalArgumentException("The view is not associated with SimpleToolbarBehavior");
              } else {
                  return (SimpleToolBarAlphaBehavior) behavior;
              }
          }
      }
    }
    

    layout 文件的写法 只要CollapsingToolbarLayout 内部有高度,那么就会自己实现渐变改变颜色

    <androidx.coordinatorlayout.widget.CoordinatorLayout
          android:layout_width="match_parent"
          android:layout_height="match_parent">
          <com.google.android.material.appbar.AppBarLayout
              android:id="@+id/app_bar_layout"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              app:layout_behavior=".widget.behavior.AppBarLayoutBehavior"
              app:elevation="0dp">
    
              <com.google.android.material.appbar.CollapsingToolbarLayout
                  android:id="@+id/collaps_tool_bar"
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  app:layout_scrollFlags="scroll|exitUntilCollapsed"
                  android:minHeight="50dp">
                  <TextView
                      android:layout_width="match_parent"
                      android:layout_height="250dp"
                      android:text="测试文本"
                      android:textColor="@color/white"
                      android:textSize="20sp"
                      android:gravity="center"/>
              </com.google.android.material.appbar.CollapsingToolbarLayout>
          </com.google.android.material.appbar.AppBarLayout>
          <androidx.core.widget.NestedScrollView
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:background="@color/red"
              app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
    
    
              <LinearLayout
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  android:orientation="vertical">
    
                  <TextView
                      android:layout_width="match_parent"
                      android:layout_height="100dp"
                      android:background="@color/blue"/>
    
    
    
                  <TextView
                      android:layout_width="match_parent"
                      android:layout_height="1500dp"/>
              </LinearLayout>
    
          </androidx.core.widget.NestedScrollView>
    
          <TitleBarView
              android:id="@+id/zr_toolbar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              app:fillStatusBar="false"
              app:showBottomLine="false"
              android:background="@color/white"
              app:layout_scrollFlags="scroll"
              app:leftType="imageButton"
              app:layout_constraintTop_toTopOf="parent"
              app:rightType="imageButton"
              app:centerText="头部下拉"
              app:centerType="textView"
              app:centerTextColor="@color/white"
              app:layout_behavior="com.tsm.awesome.ui.widget.behavior.SimpleToolBarAlphaBehavior"
              app:rightImageResource="@drawable/icon_share_24_c2"
              app:leftImageResource="@drawable/icon_back_24_c2"/>
    
      </androidx.coordinatorlayout.widget.CoordinatorLayout>
    
    

    这样就做到了整个滑动渐变对外是完全没有感知的

    7.View 的显示和隐藏 GONE VISIBLE 与 alpha 比较

    实现View 的显示和隐藏不止有 GONE VISIBLE ,修改alpha 有时也能达到相同的效果,我在使用ViewDragHelper 的过程中,如果拖动后改变View 的 visibility ,那么本次拖动就失效了,原因是 在拖动过程中使用的是类似ViewCompat.offsetTopAndBottom() 这种方法,这个方法虽然能实现非常丝滑的View 的平移,但是如果重新layout后,他就会重新回到原位,很不幸的是 修改visibility 后,由于宽高收到的了影响,ViewGroup要重新layout ,就导致失效了, 这时使用alpha就是一个非常好的选择,

    8.clipChildren 裁剪Child

    我相信大家的开发的过程中肯定会遇到ViewPager 居中,但是能看到左右两边的一部分,给需要留边的ViewGroup 添加android:clipChildren=true 这个属性就可以做到,
    大致类似下面的效果,


    image.png

    其实类似这些技巧还有非常多,只不过不实际使用想不起来用哪些,自己想了一些就记录下来了,

    相关文章

      网友评论

        本文标题:Android 进阶学习(三十四) Android 中一些常用的

        本文链接:https://www.haomeiwen.com/subject/fgloartx.html