项目 一简介

48 阅读8分钟

这篇文章是入职公司后看完项目后写的主要是加深印象和技术的巩固吧,在经历几场面试后更加的明白技术和项目经历的重要性

一.首先说的的是音视频页面也是本人非常的感兴趣的一个方向之一 我就先说一说短视频这个模块的功能 在这个模块 头布局是引用的一个xml文件在这个头布局文件中设置模块的名称 和视频历史观看记录 消息通知的三个控件的设置

image.png 这三个控件就不多说了 下面就着重说下标签和viewpaper的联动和做了哪些的优化

image.png

这里我的标签使用的是自己自定义的没有使用常见的TabLayout去和viewpaper进行联动 在里的标签使用的是一个整体的LinearLayout横向局部在里面在嵌套了四个线性布局每一个线性布局里面放置了一个textview文字控件和一个View下划线的控件确保滑动到那个页面的时候对应的标签下面都是有下滑线的

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    android:orientation="vertical"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white">
    <include layout="@layout/a_view_actionbar_two" />

    <LinearLayout
        android:id="@+id/ll_tag"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:paddingBottom="12dp">
        <LinearLayout
            android:id="@+id/ll_tab1"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="0.25"
            android:gravity="center_horizontal"
            android:orientation="vertical">
            <TextView
                android:id="@+id/tab1"
                style="@style/trill_tag_style_text"
                android:text="@string/video_all"
                />
            <View android:layout_width="match_parent"
                android:layout_height="2dp"
                android:layout_marginTop="5dp"
                android:background="@color/black"
                android:visibility="visible"/>
        </LinearLayout>

        <LinearLayout
            android:id="@+id/ll_tab2"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="0.25"
            android:gravity="center_horizontal"
            android:orientation="vertical">
            <TextView
                android:id="@+id/tab2"
                style="@style/trill_tag_style_text"
                android:text="@string/video_food" />
            <View android:layout_width="match_parent"
                android:layout_height="2dp"
                android:layout_marginTop="5dp"
                android:background="@color/black"
                android:visibility="gone"/>
        </LinearLayout>
        <LinearLayout
            android:id="@+id/ll_tab3"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="0.25"
            android:gravity="center_horizontal"
            android:orientation="vertical">
            <TextView
                android:id="@+id/tab3"
                style="@style/trill_tag_style_text"
                android:text="@string/video_scenery" />
            <View android:layout_width="match_parent"
                android:layout_height="2dp"
                android:layout_marginTop="5dp"
                android:background="@color/black"
                android:visibility="gone"/>
        </LinearLayout>

        <LinearLayout
            android:id="@+id/ll_tab4"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="0.25"
            android:gravity="center_horizontal"
            android:orientation="vertical">
            <TextView
                android:id="@+id/tab4"
                style="@style/trill_tag_style_text"
                android:text="@string/video_culture" />
            <View android:layout_width="match_parent"
                android:layout_height="2dp"
                android:layout_marginTop="5dp"
                android:background="@color/black"
                android:visibility="gone"/>
        </LinearLayout>
    </LinearLayout>
    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <androidx.viewpager.widget.ViewPager
            android:id="@+id/wq_view_pager"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </FrameLayout>
</LinearLayout>
```这是一个完整的布局

在点击标签切换ViewPaper页面的时候通过每个标签的ID进行切换ViewPaPer在滑动切换页面的时候都是会重置标签选中的状态的 当ViewPager页面切换完成时,会调用onPageSelected()方法,进而调用resetLabFocus(position)方法更新标签的选中状态。

/**

  • 重置tab选中状态
  • @param pos */ private void resetLabFocus(int pos){ for(int i = 0;i < 4;i++){ ViewGroup llTab = (ViewGroup) tabs[i].getParent(); if(i == pos){ tabs[i].setTypeface(Typeface.defaultFromStyle(Typeface.BOLD)); llTab.getChildAt(1).setVisibility(View.VISIBLE); }else { tabs[i].setTypeface(Typeface.defaultFromStyle(Typeface.NORMAL)); llTab.getChildAt(1).setVisibility(View.GONE); } } if(videoListArray[pos] == null || videoListArray[pos].size() == 0){ loadData(false,pos); } }

mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageSelected(int position) {

    resetLabFocus(position);
}
@Override
public void onPageScrollStateChanged(int state) {
}

}); for(TextView v : tabs){ v.setOnClickListener(tabsOnClickListener); }

private View.OnClickListener tabsOnClickListener = new View.OnClickListener() { @Override public void onClick(View v) { switch (v.getId()){ case R.id.tab1: mViewPager.setCurrentItem(0); break; case R.id.tab2: mViewPager.setCurrentItem(1); break; case R.id.tab3: mViewPager.setCurrentItem(2); break; case R.id.tab4: mViewPager.setCurrentItem(3); break; } } };

@Override public void onPageSelected(int position) { if (mOnOutPageChangeListener != null) { mOnOutPageChangeListener.onPageSelected(position); } updateTitleStyle(position); if (mPagerIndicatorMode == INDICATOR_MODE_SCROLLABLE && mPagerIndicatorScrollToCenterMode == PAGER_INDICATOR_SCROLL_TO_CENTER_MODE_TRANSACTION) { transactionScrollTitleParentToCenter(position); } }

下面我这说一说视频列表方面的优化优化和页面的一些问题吧(本人小白,刚开始搞安卓如有不对的地方欢迎大神指点批评) 视频页面的数据是从本地上传到服务器中网络请求到本地视频列表中,在视频的列表中使用的是RecyclerView加listview布局后来改成只使用RecyclerView了 因为开始的时候是前四条数据一个布局后面的时候一个布局当时做这个布局的时候遇到最大的一个问题就是滑动冲突因为网上滑动的时候外部滑不动 还有点卡顿 主要是事件分发机制的抢占 外层的ScrollView会优先拦截触摸事件onInterceptTouchEvent并尝试自己处理垂直滑动导致内层的RecyclerView无法接收到事件无法正常的滑动

我就说下我是怎么解决的吧 当时在博客上也是看了一些文章了解有外部拦截和内部拦截两种二我去使用的是内部拦截将RecyclerView的滑动禁用了使用setNestedScrollingEnabled(false)方法禁止RecyclerView的嵌套滑动确保只使用父控件的滑动 通过重写父容器的onInterceptTouchEvent方法,根据滑动方向决定事件分配: @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (isVerticalScroll(ev)) { return true; // 父容器处理垂直滑动 } return super.onInterceptTouchEvent(ev); } 或者是可以使用支持嵌套滑动的NestedScrollView替代普通ScrollView,并配合NestedScrollingChild接口实现协调滑动

事件总线分发 事件分发机制是处理用户触摸事件早View层级中传递拦截和消费的核心机制原理基于责任链模式通过三层核心方法实现事件的传递和处理

MotionEvent对象
触摸事件封装为MotionEvent对象,包含以下主要类型:

  • ACTION_DOWN:手指按下

  • ACTION_MOVE:手指移动

  • ACTION_UP:手指抬起

  • ACTION_CANCEL:事件被取消(如父容器拦截)
    一个完整事件序列以DOWN开始,经过多个MOVE,最终以UPCANCEL结束

    事件从顶层到叶子View的传递路径 Activity → Window → DecorView → ViewGroup → View

Activity‌:事件入口,通过dispatchTouchEvent()将事件传递给Window ViewGroup‌:中转站,决定是否拦截事件或分发给子View。 View‌:最终消费者,处理或返回事件

dispatchTouchEvent(MotionEvent) ‌ ‌ 作用:分发事件,所有View均需实现

 -**ViewGroup**‌:先调用`onInterceptTouchEvent()`判断是否拦截;若未拦截则遍历子View分发;若拦截或子View未处理,调用自身`onTouchEvent()。
      
-   ‌**View**‌:直接调用`onTouchEvent()。

-**onInterceptTouchEvent(MotionEvent)** -**作用**‌:ViewGroup特有,决定是否拦截事件(默认返回`false`不拦截)。
  -**拦截后果**‌:事件不再传递子View,由当前ViewGroup的`onTouchEvent()`处理。

-**onTouchEvent(MotionEvent)**-**作用**‌:处理事件,返回`true`表示消费事件,终止传递;返回`false`则事件回溯至上层

Activity优先将事件传递给Window(即DecorView)。 ‌ViewGroup分发逻辑

onInterceptTouchEvent()返回true,事件交由自身onTouchEvent()处理。 若返回false,遍历子View的dispatchTouchEvent(),直到有View消费事件。

View处理事件

onTouchEvent()返回true,事件终止;否则回溯至父容器的onTouchEvent()

RecyclerView视频列表加载数据过多卡顿问题优化

使用分页加载每次加载数据的时候加载10条数据

可以使用RecyclerView的ViewHolder复用机制 // 在适配器中正确实现ViewHolder复用

public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    // 根据viewType创建不同的ViewHolder
    switch (viewType) {
        case TYPE_HEADER:
            return new HeaderViewHolder(inflater.inflate(R.layout.header_layout, parent, false));
        case TYPE_ITEM:
            return new ItemViewHolder(inflater.inflate(R.layout.item_layout, parent, false));
        default:
            return null;
    }
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    // 在这里绑定数据,避免创建新的对象
    // 复用已有的ViewHolder,只更新数据
}
}

通过监听RecyclerView的滑动状态,在用户接近底部时提前加载更多数据:

mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
    //当前RecyclerView显示出来的最后一个的item的position
     int lastPosition = -1;
    //当前状态为停止滑动状态SCROLL_STATE_IDLE时
    if (newState == RecyclerView.SCROLL_STATE_IDLE) {
        RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager) {
            //通过LayoutManager找到当前显示的最后的item的position
            lastPosition = ((GridLayoutManager) layoutManager).findLastVisibleItemPosition();
        } else if (layoutManager instanceof LinearLayoutManager) {
            lastPosition = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
        } else if (layoutManager instanceof StaggeredGridLayoutManager) {
            StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
            if (lastPositions == null) {
                lastPositions = new int[staggeredGridLayoutManager.getSpanCount()];
            }
            staggeredGridLayoutManager.findLastVisibleItemPositions(lastPositions);
            lastPosition = CalculationUtil.findMax(lastPositions);
        }
        // 判断界面显示的最后item的position是否等于itemCount总数-1也就是最后一个item的position
        // 如果相等则说明已经滑动到最后了
        if (lastPosition == recyclerView.getLayoutManager().getItemCount() - 1 && !isLoad) {
            pagerIndex++;
            position = lastPosition;
            loadData(false);
        }
    }
}
 });
 

通过监听View的可见性状态来预加载数据:

@Override
public void onViewAttachedToWindow(@NonNull GameViewHolder holder) {
super.onViewAttachedToWindow(holder);
// 当View进入屏幕时,预加载相邻项
int position = holder.getAdapterPosition();
if (position != RecyclerView.NO_POSITION) {
    preloadAdjacentGroups(position);
}
}

项目中使用Glide进行图片加载和缓存: // 在GameAdapter中使用Glide加载图片

  Glide.with(context)
   .load(game.getGameIcon())
 .diskCacheStrategy(DiskCacheStrategy.ALL) // 缓存所有版本的图片
 .into(imageView);

通过线程池预加载相邻项的图片资源: // 添加线程池用于预加载

private ExecutorService preloadExecutor = Executors.newFixedThreadPool(3); /**

  • 预加载相邻组的图片资源
  • @param currentPosition 当前组的位置 */ private void preloadAdjacentGroups(int currentPosition) { preloadExecutor.execute(() -> { // 预加载前后各1个组的图片 for (int i = Math.max(0, currentPosition - 1); i <= Math.min(gameGroups.size() - 1, currentPosition + 1); i++) { if (i != currentPosition) { GameGroup group = gameGroups.get(i); if (group != null && !group.getGames().isEmpty()) { // 预加载该组的前几个游戏图片 int preloadCount = Math.min(3, group.getGames().size()); for (int j = 0; j < preloadCount; j++) { GameModel.Game game = group.getGames().get(j); if (game != null && game.getGameIcon() != null) { // 使用Glide预加载图片到缓存 // Glide.with(context).load(gameUrl).preload(); } } } } } }); }

使用SmartRefreshLayout实现分页加载:

<com.scwang.smartrefresh.layout.SmartRefreshLayout
android:id="@+id/refreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:srlEnableAutoLoadMore="true"
app:srlEnableLoadMore="true">

<com.yanzhenjie.recyclerview.SwipeRecyclerView
    android:id="@+id/recyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white"
    android:overScrollMode="never" />
</com.scwang.smartrefresh.layout.SmartRefreshLayout>

内存优化及时释放不需要的资源:

// 在适配器中实现资源回收

@Override
public void onViewRecycled(@NonNull RecyclerView.ViewHolder holder) {
super.onViewRecycled(holder);
// 释放图片资源等
if (holder instanceof MyViewHolder) {
    Glide.with(context).clear(((MyViewHolder) holder).imageView);
}
}

/**
  • 当ViewHolder被回收时调用此方法
  • 用于释放ViewHolder中占用的资源,避免内存泄漏和资源浪费
  • @param holder 被回收的ViewHolder实例 */ @Override public void onViewRecycled(@NonNull RecyclerView.ViewHolder holder) { super.onViewRecycled(holder); // 释放图片资源等占用的内存 // 当ViewHolder被回收时,清除其中的图片加载任务,防止图片错位显示和内存泄漏 if (holder instanceof MyViewHolder) { // 使用Glide清理ImageView中的图片资源 // clear()方法会取消正在进行的加载任务并释放相关资源 Glide.with(context).clear(((MyViewHolder) holder).imageView); } }