网易云音乐首页布局如何做的?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网易云音乐首页布局如何做的?相关的知识,希望对你有一定的参考价值。

从整个布局来讲,最外层可以使用DrawerLayout。然后嵌套一个Toolbar(顶部导航栏),一个RelativeLayout嵌套RecyclerView(抽屉侧滑栏)。还有一个ViewPager。自己亲手做了一遍,遇到了三个问题:1.顶部导航栏怎么添加三个tab,并与ViewPager联动。2.抽屉很明显不是简单的NavigationView(从下面底下的设置、退出应用两个按钮可以看出)。3.抽屉怎么顶到状态栏,并不让状态栏半透明显示。顶部导航栏依旧使用Toolbar,但是里面包裹一个TabLayout,使用TabLayout.addTab三个Tab,但是三个Tab只设置图标,不设置标题。抽屉使用的是一个RelativeLayout嵌套一个RecyclerView和一个LinearLayout(底部设置、退出应用)。先说结果,activity实现从实现上考虑,我也觉得使用Fragment会更好一些,只要在Container Activity注册广播监听播放状态就行了。而都用Activity的话,需要封装BaseActivity在OnResume注册监听、在onPause取消监听,然后界面也每个Activity都会有一个状态栏,实现起来感觉还是会比上一个方案麻烦。那有没有可能是WindowManager呢,比如360的悬浮窗就是用它实现的,来看下。页面结构是这样的,主页面为例,还是使用android Device Monitor。在里面有显示节点信息,那就不是WindowManager了。也就是说基本可以断定是Activity了。写一个BaseActivity,封装统一处理播放栏各种状态的方法,需要显示播放栏的子Activity都继承这个BaseActivity就可以了。至于系统性能,这个还好。那为什么用Activity呢?通过Jadx反编译,看下activity声明。或许只是网易的开发同学比较偏好这种方式吧。

参考技术A

很多设计大神在做分享和知识分享会的时候,都会拿网易的设计作品来作为教学案例。确实如此。网易的设计在国内都是口碑的。无论是PC端的web设计规范、还是移动端的APP视觉设计规范都是我们学习的案例。因为此处的侧边抽屉式面板是自定义的模块,而并非系统自带的控件。从截图可以看出是ios的系统,ios系统并没有面包屑抽屉这种控件。既然是自己写的面板,那自然是想加入什么元素都行,不受系统限制。可以看看别的案例,例如wps和qq也有自定义的侧面板,它们都是自定义的,面板内容可以因需而定,非常自由。

参考技术B

下面那个播放栏半透明,所以应该是叠在主界面上,主界面用 viewgroup应该比较方便,然后下面的播放栏在切换界面时根据不同的情况选择visible,gone就行了。最近发现可以直接用一个悬浮窗悬浮在所有窗口的上面,然后动态控制显示隐藏,这样就不用所有页面都去添加一个view。因为现在很多手机默认都是会禁用悬浮窗的,所以我修改了下type。WindowManager.LayoutParams(WindowManager.LayoutParams.TYPE_TOAST);这句话是关键,将悬浮窗的类型设置为Toast,这样就不需要权限就能显示。在小米手机上亲测可用。activity+fragment是一种实现的方法,模仿过,不过界面太丑。

3.Android高仿网易云音乐-首页复杂发现界面布局和功能

0.效果图

效果图依次为发现界面顶部,包含首页轮播图,水平滚动的按钮,推荐歌单;然后是发现界面推荐单曲,点击单曲就是直接进入播放界面;最后是全局播放控制条上点击播放列表按钮显示的播放列表弹窗。

1.整体分析

整体使用RecycerView实现,每个不同的块是一个Item,例如:轮播图是一个Item,按钮也是,推荐歌单和下面的歌单是,推荐单曲,还有最后的自定义首页那块也是一样。

提示:之所以把推荐歌单下面的歌单和推荐歌单标题放一个Item,主要是首页要实现自定义顺序功能,更方便管理。

2.轮播图

2.1 布局

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_height="wrap_content"
    android:layout_margin="@dimen/padding_outer">

    <com.youth.banner.Banner
        android:id="@+id/banner"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintDimensionRatio="H,0.389"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

2.2 显示数据

//banner
BannerData data = (BannerData) d;

Banner bannerView = holder.getView(R.id.banner);

BannerImageAdapter<Ad> bannerImageAdapter = new BannerImageAdapter<Ad>(data.getData()) 

    @Override
    public void onBindView(BannerImageHolder holder, Ad data, int position, int size) 
        ImageUtil.show(getContext(), (ImageView) holder.itemView, data.getIcon());
    
;

bannerView.setAdapter(bannerImageAdapter);

bannerView.setOnBannerListener(onBannerListener);

bannerView.setBannerRound(DensityUtil.dip2px(getContext(), 10));

//添加生命周期观察者
bannerView.addBannerLifecycleObserver(fragment);

bannerView.setIndicator(new CircleIndicator(getContext()));

按钮

3.1 布局

<?xml version="1.0" encoding="utf-8"?>
<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingVertical="@dimen/padding_outer"
    android:scrollbars="none">

    <LinearLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:paddingHorizontal="@dimen/padding_meddle">

    </LinearLayout>
</HorizontalScrollView>

3.2 显示数据

LinearLayout container = holder.getView(R.id.container);
if (container.getChildCount() > 0) 
    //已经添加了
    return;


//横向显示5个半
float containerWidth = ScreenUtil.getScreenWith(container.getContext()) - DensityUtil.dip2px(container.getContext(), 10 * 2);
int itemWidth = (int) (containerWidth / 5.5);
DiscoveryButtonBinding binding;
LinearLayout.LayoutParams layoutParams;
for (IconTitleButtonData it : data.getData()) 
    binding = DiscoveryButtonBinding.inflate(LayoutInflater.from(getContext()));
    binding.icon.setImageResource(it.getIcon());
    binding.title.setText(it.getTitle());

    if (it.getIcon() == R.drawable.day_recommend) 
        SuperViewUtil.show(binding.more);

        //显示日期
        binding.more.setText(String.valueOf(SuperDateUtil.currentDay()));
    

    //设置点击事件
    binding.getRoot().setOnClickListener(new View.OnClickListener() 
        @Override
        public void onClick(View v) 

        
    );

    layoutParams = new LinearLayout.LayoutParams(itemWidth, ViewGroup.LayoutParams.WRAP_CONTENT);
    container.addView(binding.getRoot(), layoutParams);

4.推荐歌单

4.1 布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <include layout="@layout/item_discovery_title" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingHorizontal="@dimen/padding_outer"
        android:paddingBottom="@dimen/d5" />
</LinearLayout>

4.2 显示数据

private void bindSheetData(BaseViewHolder holder, SheetData data) 
    //设置标题,将标题放到每个具体的item上,好处是方便整体排序
    holder.setText(R.id.title, R.string.recommend_sheet);

    //显示更多容器
    holder.setVisible(R.id.more, true);
    holder.getView(R.id.more).setOnClickListener(v -> 

    );

    RecyclerView listView = holder.getView(R.id.list);
    if (listView.getAdapter() == null) 
        //设置显示3列
        GridLayoutManager layoutManager = new GridLayoutManager(listView.getContext(), 3);
        listView.setLayoutManager(layoutManager);

        sheetAdapter = new SheetAdapter(R.layout.item_sheet);

        //item点击
        sheetAdapter.setOnItemClickListener(new OnItemClickListener() 
            @Override
            public void onItemClick(@NonNull BaseQuickAdapter<?, ?> adapter, @NonNull View view, int position) 
                if (discoveryAdapterListener != null) 
                    discoveryAdapterListener.onSheetClick((Sheet) adapter.getItem(position));
                
            
        );
        listView.setAdapter(sheetAdapter);

        GridDividerItemDecoration itemDecoration = new GridDividerItemDecoration(getContext(), (int) DensityUtil.dip2px(getContext(), 5F));
        listView.addItemDecoration(itemDecoration);
    

    sheetAdapter.setNewInstance(data.getData());

5. 底部

5.1 布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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="wrap_content"
    android:layout_marginVertical="@dimen/padding_outer"
    android:gravity="center_horizontal"
    android:orientation="vertical">

    <androidx.appcompat.widget.LinearLayoutCompat
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center_vertical">

        <TextView
            android:id="@+id/refresh_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:drawableLeft="@drawable/refresh"
            android:gravity="center_vertical"
            android:text="@string/click_refresh"
            android:textColor="@color/link"
            android:textSize="@dimen/text_small" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="@dimen/padding_small"
            android:text="@string/change_content"
            android:textColor="@color/black80"
            android:textSize="@dimen/text_small" />
    </androidx.appcompat.widget.LinearLayoutCompat>

    <com.google.android.material.button.MaterialButton
        android:id="@+id/custom"
        style="@style/Widget.MaterialComponents.Button.UnelevatedButton"
        android:layout_width="wrap_content"
        android:layout_height="@dimen/d30"
        android:layout_marginTop="@dimen/padding_outer"
        android:backgroundTint="?attr/colorSurface"
        android:insetTop="0dp"
        android:insetBottom="0dp"
        android:text="@string/custom_discovery"
        android:textColor="@color/black80"
        android:textSize="@dimen/text_small"
        app:cornerRadius="@dimen/d15"
        app:elevation="0dp"
        app:strokeColor="@color/black80"
        app:strokeWidth="@dimen/d0_5" />
</LinearLayout>

5.2 显示数据

holder.getView(R.id.refresh_button).setOnClickListener(v -> discoveryAdapterListener.onRefreshClick());
holder.getView(R.id.custom).setOnClickListener(v -> discoveryAdapterListener.onCustomDiscoveryClick());

6.迷你控制条

他是一个自定义Fragment,哪里要显示就放到哪里就行了。

7.播放列表弹窗

/**
 * 播放列表对话框
 */
public class MusicPlayListDialogFragment extends BaseViewModelBottomSheetDialogFragment<FragmentDialogAudioPlayListBinding> 

    ...

    @Override
    protected void initListeners() 
        super.initListeners();
        //删除所有按钮点击
        binding.deleteAll.setOnClickListener(new View.OnClickListener() 
            @Override
            public void onClick(View v) 
                //关闭对话框
                dismiss();

                //删除全部音乐
                getMusicListManager().deleteAll();
            
        );

        //item中子控件点击
        //删除按钮点击
        adapter.addChildClickViewIds(R.id.delete);

        adapter.setOnItemChildClickListener(new OnItemChildClickListener() 
            @Override
            public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) 
                //由于这里只有一个按钮点击
                //所以可以不判断
                if (R.id.delete == view.getId()) 
                    //删除按钮点击
                    removeItem(position);
                
            
        );

        //循环模式点击
        binding.loopModel.setOnClickListener(new View.OnClickListener() 
            @Override
            public void onClick(View v) 
                //更改循环模式
                getMusicListManager().changeLoopModel();

                //显示循环模式
                showLoopModel();

            
        );

        //设置item点击事件
        adapter.setOnItemClickListener(new OnItemClickListener() 
            @Override
            public void onItemClick(BaseQuickAdapter adapter, View view, int position) 
                //关闭dialog
                //可以根据具体的业务逻辑来决定是否关闭
                dismiss();

                //播放点击的这首音乐
                getMusicListManager().play(getMusicListManager().getDatum().get(position));
            
        );

    

    private void removeItem(int position) 
        adapter.removeAt(position);

        //从列表管理器中删除
        getMusicListManager().delete(position);

        showCount();
    

    /**
     * 显示循环模式
     */
    private void showLoopModel() 
        PlayListUtil.showLoopModel(getMusicListManager().getLoopModel(), binding.loopModel);
    

    private void showCount() 
        binding.count.setText(String.format("(%d)", getMusicListManager().getDatum().size()));
    


感谢你的阅读,更多文章请关注我们,点击,评论,转发支持。

以上是关于网易云音乐首页布局如何做的?的主要内容,如果未能解决你的问题,请参考以下文章

3.Android高仿网易云音乐-首页复杂发现界面布局和功能

3.Android高仿网易云音乐-首页复杂发现界面布局和功能

3.Android高仿网易云音乐-首页复杂发现界面布局和功能

使用flex进行网易云音乐界面构建和布局解析

iOS网易云音乐首页源码动画引擎源码等

用flex进行网易云音乐界面构建和布局解析