欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android?TextView跑馬燈實現(xiàn)原理及方法實例

 更新時間:2022年05月07日 15:58:34   作者:guodongAndroid  
字的跑馬燈效果在移動端開發(fā)中是一個比較常見的需求場景,下面這篇文章主要給大家介紹了關(guān)于Android?TextView跑馬燈實現(xiàn)原理及方法的相關(guān)資料,需要的朋友可以參考下

前言

自定義View實現(xiàn)的跑馬燈一直沒有實現(xiàn)類似 Android TextView 的跑馬燈首尾相接的效果,所以一直想看看Android TextView 的跑馬燈是如何實現(xiàn)

本文主要探秘 Android TextView 的跑馬燈實現(xiàn)原理及實現(xiàn)自下往上效果的跑馬燈

探秘

TextView#onDraw

原生 Android TextView 如何設(shè)置開啟跑馬燈效果,此處不再描述,View 的繪制都在 onDraw 方法中,這里直接查看 TextView#onDraw() 方法,刪減一些不關(guān)心的代碼

 protected void onDraw(Canvas canvas) {
     // 是否需要重啟啟動跑馬燈
     restartMarqueeIfNeeded();
 ?
     // Draw the background for this view
     super.onDraw(canvas);
         
     // 刪減不關(guān)心的代碼
 ?
     // 創(chuàng)建`mLayout`對象, 此處為`StaticLayout`
     if (mLayout == null) {
         assumeLayout();
     }
 ?
     Layout layout = mLayout;
 ?
     canvas.save();
 ?
     // 刪減不關(guān)心的代碼
 ?
     final int layoutDirection = getLayoutDirection();
     final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
 ?
     // 判斷跑馬燈設(shè)置項是否正確
     if (isMarqueeFadeEnabled()) {
         if (!mSingleLine && getLineCount() == 1 && canMarquee()
               && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
            final int width = mRight - mLeft;
            final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
            final float dx = mLayout.getLineRight(0) - (width - padding);
            canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
         }
 ?
         // 判斷跑馬燈是否啟動
         if (mMarquee != null && mMarquee.isRunning()) {
             final float dx = -mMarquee.getScroll();
             // 移動畫布
             canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
         }
     }
 ?
     final int cursorOffsetVertical = voffsetCursor - voffsetText;
 ?
     Path highlight = getUpdatedHighlightPath();
     if (mEditor != null) {
         mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
     } else {
         // 繪制文本
         layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
     }
 ?
     // 判斷是否可以繪制尾部文本
     if (mMarquee != null && mMarquee.shouldDrawGhost()) {
         final float dx = mMarquee.getGhostOffset();
         // 移動畫布
         canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
         // 繪制尾部文本
         layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
     }
 ?
     canvas.restore();
 }

Marquee

根據(jù) onDraw() 方法分析,跑馬燈效果的實現(xiàn)主要依賴 mMarquee 這個對象來實現(xiàn),好的,看下 Marquee 吧,Marquee 代碼較少,就貼上全部源碼吧

 private static final class Marquee {
     // TODO: Add an option to configure this
     // 縮放相關(guān),不關(guān)心此字段
     private static final float MARQUEE_DELTA_MAX = 0.07f;
     
     // 跑馬燈跑完一次后多久開始下一次
     private static final int MARQUEE_DELAY = 1200;
     
     // 繪制一次跑多長距離因子,此字段與速度相關(guān)
     private static final int MARQUEE_DP_PER_SECOND = 30;
 ?
     // 跑馬燈狀態(tài)常量
     private static final byte MARQUEE_STOPPED = 0x0;
     private static final byte MARQUEE_STARTING = 0x1;
     private static final byte MARQUEE_RUNNING = 0x2;
 ?
     // 對TextView進行弱引用
     private final WeakReference<TextView> mView;
     
     // 幀率相關(guān)
     private final Choreographer mChoreographer;
 ?
     // 狀態(tài)
     private byte mStatus = MARQUEE_STOPPED;
     
     // 繪制一次跑多長距離
     private final float mPixelsPerMs;
     
     // 最大滾動距離
     private float mMaxScroll;
     
     // 是否可以繪制右陰影, 右側(cè)淡入淡出效果
     private float mMaxFadeScroll;
     
     // 尾部文本什么時候開始繪制
     private float mGhostStart;
     
     // 尾部文本繪制位置偏移量
     private float mGhostOffset;
     
     // 是否可以繪制左陰影,左側(cè)淡入淡出效果
     private float mFadeStop;
     
     // 重復(fù)限制
     private int mRepeatLimit;
 ?
     // 跑動距離
     private float mScroll;
     
     // 最后一次跑動時間,單位毫秒
     private long mLastAnimationMs;
 ?
     Marquee(TextView v) {
         final float density = v.getContext().getResources().getDisplayMetrics().density;
         // 計算每次跑多長距離
         mPixelsPerMs = MARQUEE_DP_PER_SECOND * density / 1000f;
         mView = new WeakReference<TextView>(v);
         mChoreographer = Choreographer.getInstance();
     }
 ?
     // 幀率回調(diào),用于跑馬燈跑動
     private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() {
         @Override
         public void doFrame(long frameTimeNanos) {
             tick();
         }
     };
 ?
     // 幀率回調(diào),用于跑馬燈開始跑動
     private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
         @Override
         public void doFrame(long frameTimeNanos) {
             mStatus = MARQUEE_RUNNING;
             mLastAnimationMs = mChoreographer.getFrameTime();
             tick();
         }
     };
 ?
     // 幀率回調(diào),用于跑馬燈重新跑動
     private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
         @Override
         public void doFrame(long frameTimeNanos) {
             if (mStatus == MARQUEE_RUNNING) {
                 if (mRepeatLimit >= 0) {
                     mRepeatLimit--;
                 }
                 start(mRepeatLimit);
             }
         }
     };
 ?
     // 跑馬燈跑動實現(xiàn)
     void tick() {
         if (mStatus != MARQUEE_RUNNING) {
             return;
         }
 ?
         mChoreographer.removeFrameCallback(mTickCallback);
 ?
         final TextView textView = mView.get();
         // 判斷TextView是否處于獲取焦點或選中狀態(tài)
         if (textView != null && (textView.isFocused() || textView.isSelected())) {
             // 獲取當前時間
             long currentMs = mChoreographer.getFrameTime();
             // 計算當前時間與上次時間的差值
             long deltaMs = currentMs - mLastAnimationMs;
             mLastAnimationMs = currentMs;
             // 根據(jù)時間差計算本次跑動的距離,減輕視覺上跳動/卡頓
             float deltaPx = deltaMs * mPixelsPerMs;
             // 計算跑動距離
             mScroll += deltaPx;
             // 判斷是否已經(jīng)跑完
             if (mScroll > mMaxScroll) {
                 mScroll = mMaxScroll;
                 // 發(fā)送重新開始跑動事件
                 mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
             } else {
                 // 發(fā)送下一次跑動事件
                 mChoreographer.postFrameCallback(mTickCallback);
             }
             // 調(diào)用此方法會觸發(fā)執(zhí)行`onDraw`方法
             textView.invalidate();
         }
     }
 ?
     // 停止跑馬燈
     void stop() {
         mStatus = MARQUEE_STOPPED;
         mChoreographer.removeFrameCallback(mStartCallback);
         mChoreographer.removeFrameCallback(mRestartCallback);
         mChoreographer.removeFrameCallback(mTickCallback);
         resetScroll();
     }
 ?
     private void resetScroll() {
         mScroll = 0.0f;
         final TextView textView = mView.get();
         if (textView != null) textView.invalidate();
     }
 ?
     // 啟動跑馬燈
     void start(int repeatLimit) {
         if (repeatLimit == 0) {
             stop();
             return;
         }
         mRepeatLimit = repeatLimit;
         final TextView textView = mView.get();
         if (textView != null && textView.mLayout != null) {
             // 設(shè)置狀態(tài)為在跑
             mStatus = MARQUEE_STARTING;
             // 重置跑動距離
             mScroll = 0.0f;
             // 計算TextView寬度
             final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft()
                 - textView.getCompoundPaddingRight();
             // 獲取文本第0行的寬度
             final float lineWidth = textView.mLayout.getLineWidth(0);
             // 取TextView寬度的三分之一
             final float gap = textWidth / 3.0f;
             // 計算什么時候可以開始繪制尾部文本:首部文本跑動到哪里可以繪制尾部文本
             mGhostStart = lineWidth - textWidth + gap;
             // 計算最大滾動距離:什么時候認為跑完一次
             mMaxScroll = mGhostStart + textWidth;
             // 尾部文本繪制偏移量
             mGhostOffset = lineWidth + gap;
             // 跑動到哪里時不繪制左側(cè)陰影
             mFadeStop = lineWidth + textWidth / 6.0f;
             // 跑動到哪里時不繪制右側(cè)陰影
             mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
 ?
             textView.invalidate();
             // 開始跑動
             mChoreographer.postFrameCallback(mStartCallback);
         }
     }
 ?
     // 獲取尾部文本繪制位置偏移量
     float getGhostOffset() {
         return mGhostOffset;
     }
 ?
     // 獲取當前滾動距離
     float getScroll() {
         return mScroll;
     }
 ?
     // 獲取可以右側(cè)陰影繪制的最大距離
     float getMaxFadeScroll() {
         return mMaxFadeScroll;
     }
 ?
     // 判斷是否可以繪制左側(cè)陰影
     boolean shouldDrawLeftFade() {
         return mScroll <= mFadeStop;
     }
 ?
     // 判斷是否可以繪制尾部文本
     boolean shouldDrawGhost() {
         return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
     }
 ?
     // 跑馬燈是否在跑
     boolean isRunning() {
         return mStatus == MARQUEE_RUNNING;
     }
 ?
     // 跑馬燈是否不跑
     boolean isStopped() {
         return mStatus == MARQUEE_STOPPED;
     }
 }

好的,分析完 Marquee,跑馬燈實現(xiàn)原理豁然明亮

  • 在 TextView 開啟跑馬燈效果時調(diào)用 Marquee#start() 方法
  • 在 Marquee#start() 方法中觸發(fā) TextView 重繪,開始計算跑動距離
  • 在 TextView#onDraw() 方法中根據(jù)跑動距離移動畫布并繪制首部文本,再根據(jù)跑動距離判斷是否可以移動畫布繪制尾部文本

小結(jié)

TextView 通過移動畫布繪制兩次文本實現(xiàn)跑馬燈效果,根據(jù)兩幀繪制的時間差計算跑動距離,怎一個"妙"字了得

應(yīng)用

上面分析完原生 Android TextView 跑馬燈的實現(xiàn)原理,但是原生 Android TextView 跑馬燈有幾點不足:

  • 無法設(shè)置跑動速度
  • 無法設(shè)置重跑間隔時長
  • 無法實現(xiàn)上下跑動

以上第1、2點在上面 Marquee 分析中已經(jīng)有解決方案,接下來根據(jù)原生實現(xiàn)原理實現(xiàn)第3點上下跑動

MarqueeTextView

這里給出實現(xiàn)方案,列出主要實現(xiàn)邏輯,繼承 AppCompatTextView,復(fù)寫 onDraw() 方法,上下跑動主要是計算上下跑動的距離,然后再次重繪 TextView 上下移動畫布繪制文本

 /**
  * 繼承AppCompatTextView,復(fù)寫onDraw方法
  */
 public class MarqueeTextView extends AppCompatTextView {
 ?
     private static final int DEFAULT_BG_COLOR = Color.parseColor("#FFEFEFEF");
 ?
     @IntDef({HORIZONTAL, VERTICAL})
     @Retention(RetentionPolicy.SOURCE)
     public @interface OrientationMode {
     }
 ?
     public static final int HORIZONTAL = 0;
     public static final int VERTICAL = 1;
 ?
     private Marquee mMarquee;
     private boolean mRestartMarquee;
     private boolean isMarquee;
 ?
     private int mOrientation;
 ?
     public MarqueeTextView(@NonNull Context context) {
         this(context, null);
     }
 ?
     public MarqueeTextView(@NonNull Context context, @Nullable AttributeSet attrs) {
         this(context, attrs, 0);
     }
 ?
     public MarqueeTextView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
 ?
         TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MarqueeTextView, defStyleAttr, 0);
 ?
         mOrientation = ta.getInt(R.styleable.MarqueeTextView_orientation, HORIZONTAL);
 ?
         ta.recycle();
     }
 ?
     @Override
     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
         super.onSizeChanged(w, h, oldw, oldh);
 ?
         if (mOrientation == HORIZONTAL) {
             if (getWidth() > 0) {
                 mRestartMarquee = true;
             }
         } else {
             if (getHeight() > 0) {
                 mRestartMarquee = true;
             }
         }
     }
 ?
     private void restartMarqueeIfNeeded() {
         if (mRestartMarquee) {
             mRestartMarquee = false;
             startMarquee();
         }
     }
 ?
     public void setMarquee(boolean marquee) {
         boolean wasStart = isMarquee();
 ?
         isMarquee = marquee;
 ?
         if (wasStart != marquee) {
             if (marquee) {
                 startMarquee();
             } else {
                 stopMarquee();
             }
         }
     }
 ?
     public void setOrientation(@OrientationMode int orientation) {
         mOrientation = orientation;
     }
 ?
     public int getOrientation() {
         return mOrientation;
     }
 ?
     public boolean isMarquee() {
         return isMarquee;
     }
 ?
     private void stopMarquee() {
         if (mOrientation == HORIZONTAL) {
             setHorizontalFadingEdgeEnabled(false);
         } else {
             setVerticalFadingEdgeEnabled(false);
         }
 ?
         requestLayout();
         invalidate();
 ?
         if (mMarquee != null && !mMarquee.isStopped()) {
             mMarquee.stop();
         }
     }
 ?
     private void startMarquee() {
         if (canMarquee()) {
 ?
             if (mOrientation == HORIZONTAL) {
                 setHorizontalFadingEdgeEnabled(true);
             } else {
                 setVerticalFadingEdgeEnabled(true);
             }
 ?
             if (mMarquee == null) mMarquee = new Marquee(this);
             mMarquee.start(-1);
         }
     }
 ?
     private boolean canMarquee() {
         if (mOrientation == HORIZONTAL) {
             int viewWidth = getWidth() - getCompoundPaddingLeft() -
                 getCompoundPaddingRight();
             float lineWidth = getLayout().getLineWidth(0);
             return (mMarquee == null || mMarquee.isStopped())
                 && (isFocused() || isSelected() || isMarquee())
                 && viewWidth > 0
                 && lineWidth > viewWidth;
         } else {
             int viewHeight = getHeight() - getCompoundPaddingTop() -
                 getCompoundPaddingBottom();
             float textHeight = getLayout().getHeight();
             return (mMarquee == null || mMarquee.isStopped())
                 && (isFocused() || isSelected() || isMarquee())
                 && viewHeight > 0
                 && textHeight > viewHeight;
         }
     }
 ?
     /**
      * 仿照TextView#onDraw()方法
      */
     @Override
     protected void onDraw(Canvas canvas) {
         restartMarqueeIfNeeded();
 ?
         super.onDraw(canvas);
 ?
         // 再次繪制背景色,覆蓋下面由TextView繪制的文本,視情況可以不調(diào)用`super.onDraw(canvas);`
         // 如果沒有背景色則使用默認顏色
         Drawable background = getBackground();
         if (background != null) {
             background.draw(canvas);
         } else {
             canvas.drawColor(DEFAULT_BG_COLOR);
         }
 ?
         canvas.save();
 ?
         canvas.translate(0, 0);
 ?
         // 實現(xiàn)左右跑馬燈
         if (mOrientation == HORIZONTAL) {
             if (mMarquee != null && mMarquee.isRunning()) {
                 final float dx = -mMarquee.getScroll();
                 canvas.translate(dx, 0.0F);
             }
 ?
             getLayout().draw(canvas, null, null, 0);
 ?
             if (mMarquee != null && mMarquee.shouldDrawGhost()) {
                 final float dx = mMarquee.getGhostOffset();
                 canvas.translate(dx, 0.0F);
                 getLayout().draw(canvas, null, null, 0);
             }
         } else {
             // 實現(xiàn)上下跑馬燈
             if (mMarquee != null && mMarquee.isRunning()) {
                 final float dy = -mMarquee.getScroll();
                 canvas.translate(0.0F, dy);
             }
 ?
             getLayout().draw(canvas, null, null, 0);
 ?
             if (mMarquee != null && mMarquee.shouldDrawGhost()) {
                 final float dy = mMarquee.getGhostOffset();
                 canvas.translate(0.0F, dy);
                 getLayout().draw(canvas, null, null, 0);
             }
         }
 ?
         canvas.restore();
     }
 }

Marquee

 private static final class Marquee {
     // 修改此字段設(shè)置重跑時間間隔 - 對應(yīng)不足點2
     private static final int MARQUEE_DELAY = 1200;
 ?
     // 修改此字段設(shè)置跑動速度 - 對應(yīng)不足點1
     private static final int MARQUEE_DP_PER_SECOND = 30;
 ?
     private static final byte MARQUEE_STOPPED = 0x0;
     private static final byte MARQUEE_STARTING = 0x1;
     private static final byte MARQUEE_RUNNING = 0x2;
 ?
     private static final String METHOD_GET_FRAME_TIME = "getFrameTime";
 ?
     private final WeakReference<MarqueeTextView> mView;
     private final Choreographer mChoreographer;
 ?
     private byte mStatus = MARQUEE_STOPPED;
     private final float mPixelsPerSecond;
     private float mMaxScroll;
     private float mMaxFadeScroll;
     private float mGhostStart;
     private float mGhostOffset;
     private float mFadeStop;
     private int mRepeatLimit;
 ?
     private float mScroll;
     private long mLastAnimationMs;
 ?
     Marquee(MarqueeTextView v) {
         final float density = v.getContext().getResources().getDisplayMetrics().density;
         mPixelsPerSecond = MARQUEE_DP_PER_SECOND * density;
         mView = new WeakReference<>(v);
         mChoreographer = Choreographer.getInstance();
     }
 ?
     private final Choreographer.FrameCallback mTickCallback = frameTimeNanos -> tick();
 ?
     private final Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
         @Override
         public void doFrame(long frameTimeNanos) {
             mStatus = MARQUEE_RUNNING;
             mLastAnimationMs = getFrameTime();
             tick();
         }
     };
 ?
     /**
      * `getFrameTime`是隱藏api,此處使用反射調(diào)用,高系統(tǒng)版本可能失效,可使用某些方案繞過此限制
      */
     @SuppressLint("PrivateApi")
     private long getFrameTime() {
         try {
             Class<? extends Choreographer> clz = mChoreographer.getClass();
             Method getFrameTime = clz.getDeclaredMethod(METHOD_GET_FRAME_TIME);
             getFrameTime.setAccessible(true);
             return (long) getFrameTime.invoke(mChoreographer);
         } catch (Exception e) {
             e.printStackTrace();
             return 0;
         }
     }
 ?
     private final Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
         @Override
         public void doFrame(long frameTimeNanos) {
             if (mStatus == MARQUEE_RUNNING) {
                 if (mRepeatLimit >= 0) {
                     mRepeatLimit--;
                 }
                 start(mRepeatLimit);
             }
         }
     };
 ?
     void tick() {
         if (mStatus != MARQUEE_RUNNING) {
             return;
         }
 ?
         mChoreographer.removeFrameCallback(mTickCallback);
 ?
         final MarqueeTextView textView = mView.get();
         if (textView != null && (textView.isFocused() || textView.isSelected() || textView.isMarquee())) {
             long currentMs = getFrameTime();
             long deltaMs = currentMs - mLastAnimationMs;
             mLastAnimationMs = currentMs;
             float deltaPx = deltaMs / 1000F * mPixelsPerSecond;
             mScroll += deltaPx;
             if (mScroll > mMaxScroll) {
                 mScroll = mMaxScroll;
                 mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
             } else {
                 mChoreographer.postFrameCallback(mTickCallback);
             }
             textView.invalidate();
         }
     }
 ?
     void stop() {
         mStatus = MARQUEE_STOPPED;
         mChoreographer.removeFrameCallback(mStartCallback);
         mChoreographer.removeFrameCallback(mRestartCallback);
         mChoreographer.removeFrameCallback(mTickCallback);
         resetScroll();
     }
 ?
     private void resetScroll() {
         mScroll = 0.0F;
         final MarqueeTextView textView = mView.get();
         if (textView != null) textView.invalidate();
     }
 ?
     void start(int repeatLimit) {
         if (repeatLimit == 0) {
             stop();
             return;
         }
         mRepeatLimit = repeatLimit;
         final MarqueeTextView textView = mView.get();
         if (textView != null && textView.getLayout() != null) {
             mStatus = MARQUEE_STARTING;
             mScroll = 0.0F;
 ?
             // 分別計算左右和上下跑動所需的數(shù)據(jù)
             if (textView.getOrientation() == HORIZONTAL) {
                 int viewWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
                     textView.getCompoundPaddingRight();
                 float lineWidth = textView.getLayout().getLineWidth(0);
                 float gap = viewWidth / 3.0F;
                 mGhostStart = lineWidth - viewWidth + gap;
                 mMaxScroll = mGhostStart + viewWidth;
                 mGhostOffset = lineWidth + gap;
                 mFadeStop = lineWidth + viewWidth / 6.0F;
                 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
             } else {
                 int viewHeight = textView.getHeight() - textView.getCompoundPaddingTop() -
                     textView.getCompoundPaddingBottom();
                 float textHeight = textView.getLayout().getHeight();
                 float gap = viewHeight / 3.0F;
                 mGhostStart = textHeight - viewHeight + gap;
                 mMaxScroll = mGhostStart + viewHeight;
                 mGhostOffset = textHeight + gap;
                 mFadeStop = textHeight + viewHeight / 6.0F;
                 mMaxFadeScroll = mGhostStart + textHeight + textHeight;
             }
 ?
             textView.invalidate();
             mChoreographer.postFrameCallback(mStartCallback);
         }
     }
 ?
     float getGhostOffset() {
         return mGhostOffset;
     }
 ?
     float getScroll() {
         return mScroll;
     }
 ?
     float getMaxFadeScroll() {
         return mMaxFadeScroll;
     }
 ?
     boolean shouldDrawLeftFade() {
         return mScroll <= mFadeStop;
     }
 ?
     boolean shouldDrawTopFade() {
         return mScroll <= mFadeStop;
     }
 ?
     boolean shouldDrawGhost() {
         return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
     }
 ?
     boolean isRunning() {
         return mStatus == MARQUEE_RUNNING;
     }
 ?
     boolean isStopped() {
         return mStatus == MARQUEE_STOPPED;
     }
 }

效果

總結(jié)

到此這篇關(guān)于Android TextView跑馬燈實現(xiàn)的文章就介紹到這了,更多相關(guān)Android TextView跑馬燈內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 移動端開發(fā)之Jetpack?Hilt技術(shù)實現(xiàn)解耦

    移動端開發(fā)之Jetpack?Hilt技術(shù)實現(xiàn)解耦

    Hilt的出現(xiàn)解決前兩點問題,因為Hilt是Dagger針對Android平臺的場景化框架,比如Dagger需要我們手動聲明注入的地方,而Android聲明的地方不都在onCreate()嗎,所以Hilt就幫我們做了,除此之外還做了很多事情
    2023-02-02
  • android實現(xiàn)微信聯(lián)合登錄開發(fā)示例

    android實現(xiàn)微信聯(lián)合登錄開發(fā)示例

    本篇文章主要介紹了android實現(xiàn)微信聯(lián)合登錄開發(fā)示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-10-10
  • Flutter實現(xiàn)固定header底部滑動頁效果示例

    Flutter實現(xiàn)固定header底部滑動頁效果示例

    這篇文章主要為大家介紹了Flutter實現(xiàn)固定header底部滑動頁效果示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-12-12
  • android中把文件保存到sdcard代碼實例

    android中把文件保存到sdcard代碼實例

    這篇文章主要介紹了android中把文件保存到sdcard代碼實例,本文直接給出實現(xiàn)代碼,需要的朋友可以參考下
    2015-05-05
  • flutter升級3.7.3報錯Unable?to?find?bundled?Java?version解決

    flutter升級3.7.3報錯Unable?to?find?bundled?Java?version解決

    這篇文章主要介紹了flutter升級3.7.3報錯Unable?to?find?bundled?Java?version解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加
    2023-02-02
  • Android?藍牙BLE開發(fā)完全指南

    Android?藍牙BLE開發(fā)完全指南

    BLE藍牙的興起主要因為近年來可穿戴設(shè)備的流行,由于傳統(tǒng)藍牙功耗高不能滿足可穿戴設(shè)備對于續(xù)航的要求,所以大部分可穿戴設(shè)備采用藍牙4.0,即BLE藍牙技術(shù),這篇文章主要給大家介紹了關(guān)于Android?藍牙BLE開發(fā)的相關(guān)資料,需要的朋友可以參考下
    2021-11-11
  • 詳解Android(共享元素)轉(zhuǎn)場動畫開發(fā)實踐

    詳解Android(共享元素)轉(zhuǎn)場動畫開發(fā)實踐

    本篇文章主要介紹了詳解Android(共享元素)轉(zhuǎn)場動畫開發(fā)實踐,具有一定的參考價值,感興趣的小伙伴們可以參考一下。
    2017-08-08
  • Android入門之AlertDialog用法實例分析

    Android入門之AlertDialog用法實例分析

    這篇文章主要介紹了Android入門之AlertDialog用法,對Android初學(xué)者有很多的借鑒學(xué)習(xí)之處,需要的朋友可以參考下
    2014-08-08
  • Recyclerview添加頭布局和尾布局、item點擊事件詳解

    Recyclerview添加頭布局和尾布局、item點擊事件詳解

    這篇文章主要為大家詳細介紹了Recyclerview添加頭布局和尾布局、item點擊事件的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-08-08
  • 淺談android nexus私服的使用

    淺談android nexus私服的使用

    這篇文章主要介紹了淺談android nexus私服的使用,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-12-12

最新評論