Android 列表视频滑动自动播放—滑动过程自动播放(实现思路)

本文基于Exoplayer + PlayerView 实现列表视频显示一定比例后自动播放

首先引入google media3包

复制代码
implementation 'androidx.media3:media3-exoplayer:1.1.1'
implementation 'androidx.media3:media3-exoplayer-dash:1.1.1'
implementation 'androidx.media3:media3-ui:1.1.1'
implementation "androidx.media3:media3-common:1.1.1"

列表自动播放,我们需要监听recyclerView.addOnScrollListener(this),并实现对应方法

复制代码
onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) 中判断滑动过程中,展示视频itemVideoView比例

1、首先我们需要拿到recyclerView显示出来的itemPosition

复制代码
int firstItemPosition = linearLayoutManager.findFirstVisibleItemPosition();
int lastItemPosition = linearLayoutManager.findLastVisibleItemPosition();

2、当判断第一个(firstItemPosition)和最后一个(lastItemPosition)都大于0时候,开始遍历recyclerView数据,判断显示视频itemVideoView显示比例(因为我项目,视频播放数据是二维数组。RecyclerView1+RecyclerView2结构,所以视频播放时在里层RecyclerView2,如果不是二维,那么就RecyclerView2其实就是视频播放容器,dataItem.getVideoPosition()其实就是告诉我们RecyclerView2中第几条数据播放视频,默认是-1,表示没有视频,需要我们在网络请求后,遍历数据生成对应可以展示视频position)

复制代码
 public void recyclerViewScrollVideo() {
        if (recyclerView == null || linearLayoutManager == null || communityPostListAdapter == null) {
            return;
        }
        int firstItemPosition = linearLayoutManager.findFirstVisibleItemPosition();
        int lastItemPosition = linearLayoutManager.findLastVisibleItemPosition();
        if (firstItemPosition >= 0 && lastItemPosition >= 0) {
            DataItem dataItem = communityPostListAdapter.getData(firstItemPosition);
            CommonViewHolder viewHolder = (CommonViewHolder) recyclerView.findViewHolderForAdapterPosition(firstItemPosition);
            if (viewHolder != null) {
                //当第一个显示数据,不支持播放视频时候,需要直接遍历后面,看是否有支持视频的
                //获取里层 RecyclerView Image
                RecyclerView recyclerImage = viewHolder.getView(R.id.communal_post_header_recycler_image);
                if (dataItem != null && dataItem.getVideoPosition() >= 0 && recyclerImage != null) {
                    //获取里层视频那个itemView position
                    int videoPosition = dataItem.getVideoPosition();
                    CommonViewHolder itemViewHolder = (CommonViewHolder) recyclerImage.findViewHolderForAdapterPosition(videoPosition);
                    //获取当前视频itemBen
                    VideoAdapter videoAdapter = (CommunalImageAdapter) recyclerImage.getAdapter();
                    //获取视频数据对象
                    VideoDataBean videoDataBean = null;
                    if (videoDataBean != null) {
                        videoDataBean = videoAdapter.getData(videoPosition);
                    }
                    boolean isPlaying = ExoPlayerManager.getInstance().recyclerViewScrollVideo(recyclerImage, itemViewHolder, firstItemPosition, videoDataBean);
                    if (!isPlaying) {
                        //firstItemPosition不支持播放,则遍历RecyclerView数据判断下一个是否支持播放
                        int newFirstItemPosition = firstItemPosition + 1;
                        traversal(recyclerView, newFirstItemPosition, lastItemPosition);
                    }
                } else {
                    //有可能是多布局,则第一条数据,找不到视频播放view,那么我们需要遍历数据,从第二条数据开始找
                    int newFirstItemPosition = firstItemPosition + 1;
                    traversal(recyclerView, newFirstItemPosition, lastItemPosition);
                }

            }

        }
    }

3、当第一条不满足,开始遍历

复制代码
 /**
     * 遍历寻找,屏幕显示可以播放视频的item
     *
     * @param recycler
     * @param newFirstItemPosition
     * @param lastItemPosition
     */
    private void traversal(RecyclerView recycler, int newFirstItemPosition, int lastItemPosition) {
        //标记是否找到视频-1,表示未找到视频
        int playPosition = -1;
        for (int i = newFirstItemPosition; i <= lastItemPosition; i++) {
            DataItem dataItem = communityPostListAdapter.getData(i);
            CommonViewHolder viewHolder = (CommonViewHolder) recycler.findViewHolderForAdapterPosition(i);
            if (viewHolder != null && dataItem != null && dataItem.getVideoPosition() >= 0) {
                RecyclerView recyclerImage = viewHolder.getView(R.id.communal_post_header_recycler_image);
                if (recyclerImage != null) {
                    CommonViewHolder itemViewHolder = (CommonViewHolder) recyclerImage.findViewHolderForAdapterPosition(dataItem.getVideoPosition());
                    //获取当前视频itemBen
                    VideoAdapter videoAdapter = (CommunalImageAdapter) recyclerImage.getAdapter();
                    VideoDataBean videoDataBean = null;
                    if (videoAdapter != null) {
                        videoDataBean = videoAdapter.getData(dataItem.getVideoPosition());
                    }
                    if (ExoPlayerManager.getInstance().recyclerViewScrollVideo(recyclerImage, itemViewHolder, i, communalImageBean)) {
                        playPosition = i;
                        break;
                    }

                }

            }
        }
        if (playPosition == -1) {
            //都没有找到视频
            ExoPlayerManager.getInstance().stopVideo();
        }
    }

4、视频播放器单例(Exoplayer + PlayerView)

复制代码
public class ExoPlayerManager {

    private static volatile ExoPlayerManager inStance = null;
    ExoPlayer mExoPlayer;
    public ExoPlayerListener mExoPlayerListener;
    private UserPlayerView mUserPlayerView;
    //视频容器
    private FrameLayout mLayout;
    //loading view
    private ProgressBar mLoading;
    //视频logo
    private ImageView mLogo;
    //点击播放按钮
    private ImageView mPlay;

    //计算RecyclerView显示位置
    private Rect mItemRect;
    //RecyclerView显示高度
    float visibleHeight;
    //RecyclerView内容高度
    float totalHeight;
    //RecyclerView 显示高度和高度比例
    float visibleRatio;
    int mItemPlayPosition;
    //视频播放数据对象
    VideoDataBean mVideoDataBean;
    //视频最先显示比例
    private float minRatio;

    /**
     * 监听
     */
    private AnalyticsListener mAnalyticsListener = new AnalyticsListener() {
        @Override
        public void onPlaybackStateChanged(EventTime eventTime, int state) {
            AnalyticsListener.super.onPlaybackStateChanged(eventTime, state);
            switch (state) {
                case Player.STATE_IDLE:  //未加载到资源,停止播放,包含无网也会执行
                    if (mExoPlayerListener != null) {
                        mExoPlayerListener.onVideoPlayIdle();
                    } else {
                        stopShowView();
                    }
                    showController();
                    break;
                case Player.STATE_BUFFERING: //缓冲中
                    if (mExoPlayerListener != null) {
                        mExoPlayerListener.onVideoPlayBuffering(eventTime != null ? eventTime.currentPlaybackPositionMs : -1L);
                    } else {
                        loadingShowView();
                    }
                    break;

                case Player.STATE_READY:  //开始播放
                    if (mExoPlayerListener != null) {
                        mExoPlayerListener.onVideoPlay(eventTime != null ? eventTime.currentPlaybackPositionMs : -1L);
                    } else {
                        startShowView();
                    }

                    break;

                case Player.STATE_ENDED:  //播放结束,当循环播放时候,改方法不会被触发  onPositionDiscontinuity  DISCONTINUITY_REASON_AUTO_TRANSITION
                    if (mExoPlayerListener != null) {
                        mExoPlayerListener.onVideoPlayerCompletion();
                    }
                    break;
            }
        }

        @Override
        public void onPositionDiscontinuity(EventTime eventTime, Player.PositionInfo oldPosition, Player.PositionInfo newPosition, int reason) {
            AnalyticsListener.super.onPositionDiscontinuity(eventTime, oldPosition, newPosition, reason);
            if (mExoPlayerListener != null) {
                mExoPlayerListener.onVideoPlayerCompletion();
            } else {
                //循环播放了,显示时间
                if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION) {
                    showController();
                }
            }

        }

        @Override
        public void onPlayerError(EventTime eventTime, PlaybackException error) {
            AnalyticsListener.super.onPlayerError(eventTime, error);
            if (mExoPlayerListener != null) {
                mExoPlayerListener.onVideoPlayerError(eventTime != null ? eventTime.currentPlaybackPositionMs : -1l);
            } else {
                Toast.makeText(CommonParam.getInstance().getApplication(), "网络异常", Toast.LENGTH_LONG).show();
                if (eventTime != null) {
                    long currentPlaybackPositionMs = eventTime.currentPlaybackPositionMs;
                    if (mVideoDataBean != null && currentPlaybackPositionMs > 0) {
                        mVideoDataBean.setSeekToPositionMs(currentPlaybackPositionMs);
                    }
                }
                //网络异常会执行--onPlayerError---onPlaybackStateChanged(1)----onIsPlayingChanged()
                stopShowView();
            }
        }
    };

    public static ExoPlayerManager getInstance() {
        if (inStance == null) {
            synchronized (ExoPlayerManager.class) {
                if (inStance == null) {
                    inStance = new ExoPlayerManager();
                }
            }
        }
        return inStance;
    }

    private ExoPlayerManager() {
        init();
    }


    private void init() {
        //初始化exoPlayer
        if (mExoPlayer == null) {
            mExoPlayer = new ExoPlayer.Builder(CommonParam.getInstance().getApplication()).build();
            mExoPlayer.addAnalyticsListener(mAnalyticsListener);
        }
        // 设置重复模式
        // Player.REPEAT_MODE_ALL 无限重复
        // Player.REPEAT_MODE_ONE 重复一次
        // Player.REPEAT_MODE_OFF 不重复
        mExoPlayer.setRepeatMode(Player.REPEAT_MODE_ALL);
        setVolume(CommonParam.getInstance().isVideoVolumeChange());

        if (mItemRect == null) {
            mItemRect = new Rect();
        }
        if (mUserPlayerView == null) {
            mUserPlayerView = new UserPlayerView(CommonParam.getInstance().getApplication());
            ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
            mUserPlayerView.setLayoutParams(layoutParams);
        }
        //设置视频填充模式  RESIZE_MODE_FILL 拉伸视频达到没有黑边效果,RESIZE_MODE_ZOOM:这个是居中填充效果
        mUserPlayerView.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIXED_WIDTH);
        minRatio = 9f / 16f;
    }


    /**
     * 获取视频View
     *
     * @return
     */
    public UserPlayerView getUserPlayerView() {
        return mUserPlayerView;
    }

    /**
     * 添加监听
     */
    public void addAnalyticsListener() {
        if (mExoPlayer != null) {
            mExoPlayer.removeAnalyticsListener(mAnalyticsListener);
            mExoPlayer.addAnalyticsListener(mAnalyticsListener);
        }
    }

    /**
     * 移除监听
     */
    public void removeAnalyticsListener() {
        if (mExoPlayer != null) {
            mExoPlayer.removeAnalyticsListener(mAnalyticsListener);
        }
    }

    /**
     * 视频显示尺寸修改
     *
     * @param params
     * @param playerView
     * @param maxWidth   屏幕显示最大宽
     * @param width      视频真实宽
     * @param height     视频真实高
     */
    public void disposeVideoSize(ViewGroup.LayoutParams params, View playerView, int maxWidth, float width, float height) {
        if (params != null && playerView != null) {
            if (width > 0 && height > 0) {
                if (height >= width) {
                    int showWidth = (int) (maxWidth > 0 ? maxWidth : width);
                    params.width = showWidth;
                    params.height = showWidth;
                } else {
                    params.width = maxWidth;
                    float ratio = height / width;
                    params.height = (int) ((ratio >= minRatio) ? ratio * maxWidth : minRatio * maxWidth);
                }

            } else if (maxWidth > 0) {
                params.width = maxWidth;
                params.height = maxWidth;
            } else {
                params.width = ViewGroup.LayoutParams.MATCH_PARENT;
                params.height = ViewGroup.LayoutParams.MATCH_PARENT;
            }
            playerView.setLayoutParams(params);
        }
    }


  

    /**
     * 获取当前列表布局控件,一会计算时候需要用
     *
     * @param recyclerImage
     * @param itemViewHolder
     * @param communalImageBean
     */
    public boolean recyclerViewScrollVideo(RecyclerView recyclerImage, CommonViewHolder itemViewHolder, int itemPlayPosition, CommunalImageBean communalImageBean) {
        if (recyclerImage != null && itemViewHolder != null && communalImageBean != null) {
            FrameLayout frameLayout = itemViewHolder.getView(R.id.video);
            ImageView logo = itemViewHolder.getView(R.id.logo);
            ImageView play = itemViewHolder.getView(R.id.play);
            ProgressBar loading = itemViewHolder.getView(R.id.loading);
            ImageView volume = itemViewHolder.getView(R.id.volume);
            setVolumeViewChange(volume);
            setVolume(CommonParam.getInstance().isVideoVolumeChange());
            return calculateVideoPlay(recyclerImage, frameLayout, play, logo, loading, itemPlayPosition, communalImageBean);
        }
        return false;
    }

    /**
     * 计算是否滑动自动播放视频
     *
     * @param view
     * @param frameLayout
     * @param play
     * @param logo
     * @param loading
     * @param videoDataBean
     */
    public boolean calculateVideoPlay(View view, FrameLayout frameLayout, ImageView play, ImageView logo, ProgressBar loading, int itemPlayPosition,VideoDataBean videoDataBean ) {
        view.getLocalVisibleRect(mItemRect);
        visibleHeight = mItemRect.bottom - mItemRect.top;
        totalHeight = view.getHeight();
        visibleRatio = visibleHeight / totalHeight;
        if (mItemRect.top >= 0 && visibleRatio > BaseConstantValue.VIDEO_START_VISIBLE_RATIO) {
            playVideo(frameLayout, play, logo, loading, itemPlayPosition, videoDataBean);
            return true;
        } else {
            //不满足播放
            if (play != null) {
                play.setVisibility(View.VISIBLE);
            }
            if (loading != null) {
                loading.setVisibility(View.GONE);
            }
            if (logo != null) {
                logo.setVisibility(View.VISIBLE);
            }
            if (frameLayout != null) frameLayout.removeAllViews();

        }
        return false;
    }

    /**
     * 满足播放视频,需要再判断,是不是已经还在播放当前视频
     *
     * @param layout
     * @param play
     * @param logo
     * @param loading
     * @param itemPlayPosition
     * @param videoDataBean
     */
    public void playVideo(FrameLayout layout, ImageView play, ImageView logo, ProgressBar loading, int itemPlayPosition, VideoDataBean videoDataBean) {
        if (mItemPlayPosition != itemPlayPosition) {
            //不满足当前播放那么需要停止播放,做部分资源保存
            if (videoDataBean != null && mExoPlayer.isPlaying()) {
                //视频已经播放位置
                videoDataBean.setSeekToPositionMs(mExoPlayer.getContentPosition());
            }
            //显示暂停不播布局
            stopShowView();
            //暂停播放
            stopVideo();
        }
        //重新复制相关控件
        mLayout = layout;
        mPlay = play;
        mLogo = logo;
        mLoading = loading;

        //获取当前播放状态,如果未播放,则直接设置资源播放,如果已经播放,则不做处理
        if (!mExoPlayer.isPlaying()) {
            //设置播放布局
            loadingShowView();
            //移除播放View
            if (mUserPlayerView != null) {
                ViewParent parent = mUserPlayerView.getParent();
                if (parent != null && parent instanceof FrameLayout) {
                    ((FrameLayout) parent).removeAllViews();
                }
            }

            //将PlayerView 添加 FrameLayout上
            if (layout != null) {
                layout.removeAllViews();
                layout.addView(mUserPlayerView);
            }
            if (videoDataBean != null) {
                String videoUrl = videoDataBean.getVideoUrl();
                long seekToPositionMs = videoDataBean.getSeekToPositionMs();
                long durationMs = videoDataBean.getDurationMs();
                mUserPlayerView.setVideoPosition(seekToPositionMs, durationMs);
                MediaItem mediaItem = MediaItem.fromUri(videoUrl != null ? videoUrl : "");
                mExoPlayer.setMediaItem(mediaItem, seekToPositionMs);
            }

            mUserPlayerView.setPlayer(mExoPlayer);

            mExoPlayer.prepare();
//            mExoPlayer.play();
            mExoPlayer.setPlayWhenReady(true);
        }

        mItemPlayPosition = itemPlayPosition;
        mVideoDataBean = videoDataBean;
    }


    public ExoPlayer exoPlayer() {
        return mExoPlayer;
    }

    /**
     * 是否正在播放
     *
     * @return
     */
    public boolean isPlaying() {
        return mExoPlayer != null ? mExoPlayer.isPlaying() : false;
    }

    /**
     * 获取当前播放进度
     *
     * @return
     */
    public long getCurrentPositionMs() {
        if (mExoPlayer != null && mExoPlayer.isPlaying()) {
            return mExoPlayer.getCurrentPosition();
        }
        return -1;
    }

    /**
     * 获取总时长
     *
     * @return
     */
    public long getDurationMs() {
        if (mExoPlayer != null) {
            return mExoPlayer.getDuration();
        }
        return 0l;
    }

    /**
     * 设置进度
     *
     * @param seekToCurrentPositionMs
     */
    public void seekTo(long seekToCurrentPositionMs) {
        if (mExoPlayer != null) {
            mExoPlayer.seekTo(seekToCurrentPositionMs);
            if (!isPlaying()) {
                mExoPlayer.play();
            }
        }
    }

    /**
     * 设置是否有视频控制器
     *
     * @param useController
     */
    private void setUseController(boolean useController) {
        mUserPlayerView.setUseController(useController);

    }

    /**
     * 绑定全屏播放
     *
     * @param videoUrl
     * @param currentPositionMs
     * @return
     */
    private UserPlayerView bindVideoFullScreen(String videoUrl, long currentPositionMs) {
        mItemPlayPosition = -1;
        mExoPlayer.setMediaItem(MediaItem.fromUri(videoUrl == null ? "" : videoUrl), currentPositionMs);
        mUserPlayerView.setPlayer(mExoPlayer);
        mExoPlayer.prepare();
        mVideoDataBean = null;
        return mUserPlayerView;
    }


    /**
     * 开始播放
     */
    public void startVideo() {
        if (mExoPlayer != null && !mExoPlayer.isPlaying()) {
            mExoPlayer.play();
        }
    }

    /**
     * 开始播放
     */
    public void startVideo(long currentPositionMs) {
        if (mExoPlayer != null && !mExoPlayer.isPlaying()) {
            mExoPlayer.seekTo(currentPositionMs);
            mExoPlayer.play();
        }
    }

    /**
     * 开始播放
     */
    public void startVideo(String videoUrl, long currentPositionMs) {
        if (mExoPlayer != null) {
            if (!mExoPlayer.isPlaying()) {
                mExoPlayer.stop();
            }
            mExoPlayer.setMediaItem(MediaItem.fromUri(videoUrl == null ? "" : videoUrl), currentPositionMs);
            mUserPlayerView.setPlayer(mExoPlayer);
            mExoPlayer.prepare();
            mExoPlayer.play();
        }

    }

    /**
     * 暂停播放
     */
    public void pauseVideo() {
        if (mExoPlayer != null && mExoPlayer.isPlaying()) {
            mExoPlayer.pause();
        }
    }

    /**
     * 停止播放
     */
    public void stopVideo() {
        mItemPlayPosition = -1;
        if (mExoPlayer != null && mExoPlayer.isPlaying()) {
            if (mVideoDataBean != null) {
                mVideoDataBean.setSeekToPositionMs(mExoPlayer.getContentPosition());
            }
           
        }
 mExoPlayer.stop();
        releaseView();
    }

    /**
     * 页面关闭,释放资源
     */
    public void releaseVideo() {
        if (mExoPlayer != null) {
            mExoPlayer.removeAnalyticsListener(mAnalyticsListener);
            mExoPlayer.release();
            mExoPlayer = null;
        }
        releaseView();
    }

    private void releaseView() {
        if (mUserPlayerView != null) {
            ViewParent parent = mUserPlayerView.getParent();
            if (parent != null && parent instanceof FrameLayout) {
                ((FrameLayout) parent).removeAllViews();
            }
        }
        //将PlayerView 添加 FrameLayout上
        if (mLayout != null) {
            mLayout.removeAllViews();
            mLayout = null;
        }
        if (mPlay != null) {
            mPlay = null;
        }
        if (mLogo != null) {
            mLogo = null;
        }
        if (mLoading != null) {
            mLoading = null;
        }
        mVideoDataBean = null;
    }

    /**
     * 设置播放器是否开启声音
     *
     * @param volumeChange
     */
    public void setVolume(boolean volumeChange) {
        if (volumeChange) {
            //设置声音
            if (mExoPlayer.getVolume() == 0f) {
                AudioManager audioManager = (AudioManager) CommonParam.getInstance().getApplication().getSystemService(Context.AUDIO_SERVICE);
                float streamVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
                float streamMaxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
                float volume = (streamMaxVolume != 0 && streamVolume != 0) ? streamVolume / streamMaxVolume : 0.3f;
                mExoPlayer.setVolume(volume);
            }
        } else {
            //设置声音为0
            mExoPlayer.setVolume(0f);
        }
        CommonParam.getInstance().setVideoVolumeChange(volumeChange);
    }

    /**
     * 设置 音量开关状态
     *
     * @param volumeView
     */
    public void setVolumeViewChange(View volumeView) {
        if (volumeView != null) {
            volumeView.setSelected(CommonParam.getInstance().isVideoVolumeChange());
        }
    }

    /**
     * 视频正在播放,设置相关布局show or hide
     */
    public void startShowView() {
        if (mLoading != null) {
            mLoading.setVisibility(View.GONE);
        }
        if (mLogo != null) {
            mLogo.setVisibility(View.GONE);
        }
        if (mPlay != null) {
            mPlay.setVisibility(View.GONE);
        }
    }

    /**
     * 视频正在加载中,设置相关布局show or hide
     */
    public void loadingShowView() {
        if (mLoading != null) {
            mLoading.setVisibility(View.VISIBLE);
        }
        if (mLogo != null) {
            mLogo.setVisibility(View.VISIBLE);
        }
        if (mPlay != null) {
            mPlay.setVisibility(View.GONE);
        }
    }

    /**
     * 视频停止播放,设置相关布局show or hide
     */
    public void stopShowView() {
        if (mLoading != null) {
            mLoading.setVisibility(View.GONE);
        }
        if (mLogo != null) {
            mLogo.setVisibility(View.VISIBLE);
        }
        if (mPlay != null) {
            mPlay.setVisibility(View.VISIBLE);
        }
    }

    /**
     * 显示视频控制器
     */
    public void showController() {
        if (mUserPlayerView != null) {
            UserPlayerControlView controller = mUserPlayerView.getController();
            if (controller != null) {
                controller.show();
            }
        }
    }

    /**
     * 设置监听
     *
     * @param exoPlayerListener
     */
    public void setExoPlayerListener(ExoPlayerListener exoPlayerListener) {
        mExoPlayerListener = exoPlayerListener;
    }
}

5、建议我们在开发视频时自定义PlayerView,只需要把源码中,这三个文件拷贝一份,然后自己根据具体需求,实现对应功能。

①、PlayerView

②、PlayerControlView

③、PlayerControlViewLayoutManager

6、如果项目是appBarLayout+RecyclerView的话。我们需要对appBarLayout滑动事件进行监听,从而实现appBarLayout滑动,也能播放视频

复制代码
  mAppBarLayout.addOnOffsetChangedListener(new AppBarLayout.BaseOnOffsetChangedListener() {
            @Override
            public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
                //appBarLayout 滑动监听
                int newOffset = Math.abs(verticalOffset);
                if (mVerticalOffset != newOffset && adapter != null) {
                    Fragment fragment = adapter .getFragment(viewPagerShowType);
                    if (fragment != null && fragment.isOnResume()) {
                        fragment.startVideo();
                    }
                    mVerticalOffset = newOffset;
                }

            }
        });
相关推荐
Kapaseker31 分钟前
Compose 进阶—巧用 GraphicsLayer
android·kotlin
黄林晴1 小时前
Android17 为什么重写 MessageQueue
android
阿巴斯甜1 天前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker1 天前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95271 天前
Andorid Google 登录接入文档
android
黄林晴1 天前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab2 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿2 天前
Android MediaPlayer 笔记
android
Jony_2 天前
Android 启动优化方案
android
阿巴斯甜2 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android