Android仿抖音主页效果实现代码

写在前面

各位老铁,我又来啦!既然来了,那肯定又来搞事情啦,话不多说,先上图!

“抖音”都玩过吧,是不是很好玩,我反正是天天刷,作为一个非著名的Android低级攻城狮,虽然技术菜的一匹,但是也经常刷着刷着会思考:咦?这玩意是用哪个控件做的?这个效果是咋实现的啊?由于本人技术水平有限,所以今天咱就先挑个比较简单的来看看是如何实现的,思考再三,我们就拿抖音首页的这个效果来练练手吧,话不多说,开搞!

一、准备工作

我们先不急着写代码,先对抖音的这种效果做一个简单的分析,首先需要明确的是它是可以滑动的,并且可以上滑回去,也可以下滑到下一个,滑动的数量跟随视频的个数而定,到这里其实能实现这种效果的控件就已经被缩小到一个范围内了。初步判定可以使用ViewPager或者是RecyclerView来实现,你细想一下,它实际上就是一个列表啊,每一屏的视频效果就是一个单独的Item,并且它的列表Item的数量可以很大,至少目前你应该没有哪一次是能把抖音滑到底的吧,那最后咱们使用RecyclerView来实现这个效果。

为什么不用ViewPager?我们需要的是每次只加载一屏,ViewPager默认会有预加载机制,并且数据量较大的时候性能表现也是很差的。反之,RecyclerView最好的性能就是只加载一屏幕的Item,并且处理海量数据时性能更优,所以我们选用RecyclerView实现。

基础列表的承载控件我们已经选好了,然后通过上面的效果不难发现,每一屏里面实际上只有一个Item,所以基础的页面布局你应该也知道该怎么做了。然后就是视频播放了,由于这里我们只是仿照实现抖音的主页面效果,最核心的实际上是实现这个RecyclerView滑动的效果,所以代码我这里是尽量考虑简单化,因此视频播放就直接使用的Android原生的VideoView来做的,效果肯定不会多好,如果你对视频播放这块要求比较高的话,可以考虑使用基于ijkplayer实现的一些比较优秀的开源库,再或者能力强的自己基于ffmpeg定制开发播放器。

OK,到这里基本的分析就已经做完了,下面我们就先来实现基础代码吧!

先把需要的图片和视频文件准备好哦,别忘记了,视频这里放在res/raw目录下:

1.1、主页面布局

新建一个TiktokIndexActivity.java,创建布局文件activity_tiktok_layout.xml:

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

  <android.support.v7.widget.RecyclerView
    android:id="@+id/mRecycler"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/color_01"/>

  <RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="35dp"
    android:layout_marginTop="36dp">

    <LinearLayout
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_centerInParent="true"
      android:orientation="horizontal"
      android:gravity="center_vertical">

      <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginRight="16dp"
        android:text="南京"
        android:textColor="#f2f2f2"
        android:textSize="18sp"
        android:textStyle="bold" />

      <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginRight="16dp"
        android:text="关注"
        android:textColor="#f2f2f2"
        android:textSize="18sp"
        android:textStyle="bold" />

      <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="推荐"
        android:textColor="@android:color/white"
        android:textSize="20sp"
        android:textStyle="bold" />

    </LinearLayout>

    <ImageView
      android:layout_width="30dp"
      android:layout_height="30dp"
      android:layout_alignParentRight="true"
      android:layout_centerVertical="true"
      android:layout_marginRight="16dp"
      android:src="@drawable/search_icon"
      android:tint="#f2f2f2" />
  </RelativeLayout>

  <LinearLayout
    android:id="@+id/mBottomLayout"
    android:layout_width="match_parent"
    android:layout_height="?actionBarSize"
    android:background="@color/color_01"
    android:layout_alignParentBottom="true"
    android:gravity="center_vertical"
    android:orientation="horizontal">

    <TextView
      android:layout_width="0dp"
      android:layout_height="wrap_content"
      android:layout_weight="1"
      android:gravity="center"
      android:text="首页"
      android:textColor="@android:color/white"
      android:textSize="18sp"
      android:textStyle="bold" />

    <TextView
      android:layout_width="0dp"
      android:layout_height="wrap_content"
      android:layout_weight="1"
      android:gravity="center"
      android:text="朋友"
      android:textColor="#f2f2f2"
      android:textSize="17sp"
      android:textStyle="bold" />

    <LinearLayout
      android:layout_width="0dp"
      android:layout_height="match_parent"
      android:layout_weight="1"
      android:gravity="center">
      <ImageView
        android:layout_width="50dp"
        android:layout_height="30dp"
        android:scaleType="fitCenter"
        android:src="@drawable/icon_add" />
    </LinearLayout>

    <TextView
      android:layout_width="0dp"
      android:layout_height="wrap_content"
      android:layout_weight="1"
      android:gravity="center"
      android:text="消息"
      android:textColor="#f2f2f2"
      android:textSize="17sp"
      android:textStyle="bold" />

    <TextView
      android:layout_width="0dp"
      android:layout_height="wrap_content"
      android:layout_weight="1"
      android:gravity="center"
      android:text="我"
      android:textColor="#f2f2f2"
      android:textSize="17sp"
      android:textStyle="bold" />
  </LinearLayout>
</RelativeLayout>

1.2、列表Item布局

由于我们的VideoView想要自己设置宽和高,所以这里自定义一个VideoView,重写onMeasure()测量方法:

public class CusVideoView extends VideoView {
  public CusVideoView(Context context) {
    super(context);
  }

  public CusVideoView(Context context, AttributeSet attrs) {
    super(context, attrs);
  }

  public CusVideoView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
  }

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int width = getDefaultSize(getWidth(), widthMeasureSpec);
    int height = getDefaultSize(getHeight(), heightMeasureSpec);
    setMeasuredDimension(width, height);
  }
}

然后接着来编写每一屏Item的布局文件:item_tiktok_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  xmlns:tools="http://schemas.android.com/tools"
  android:id="@+id/mRootView"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <com.jarchie.androidui.tiktok.CusVideoView
    android:id="@+id/mVideoView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clickable="false"
    android:focusable="false" />

  <ImageView
    android:id="@+id/mThumb"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clickable="false"
    android:focusable="false"
    android:scaleType="centerCrop"
    android:visibility="visible" />

  <LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentEnd="true"
    android:layout_centerVertical="true"
    android:layout_marginRight="10dp"
    android:gravity="center"
    android:orientation="vertical">

    <RelativeLayout
      android:layout_width="wrap_content"
      android:layout_height="wrap_content">

      <de.hdodenhof.circleimageview.CircleImageView
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:layout_alignParentTop="true"
        android:src="@drawable/icon_avatar"
        app:civ_border_color="@android:color/white"
        app:civ_border_width="2dp" />

      <ImageView
        android:layout_width="20dp"
        android:layout_height="20dp"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="50dp"
        android:background="@drawable/circle_big_red"
        android:scaleType="centerInside"
        android:src="@drawable/add_icon"
        android:tint="@android:color/white" />
    </RelativeLayout>

    <TextView
      android:layout_width="50dp"
      android:layout_height="50dp"
      android:layout_marginTop="16dp"
      android:drawableTop="@drawable/heart_icon"
      android:gravity="center"
      android:text="8.88w"
      android:textColor="@android:color/white" />

    <TextView
      android:layout_width="50dp"
      android:layout_height="50dp"
      android:layout_marginTop="16dp"
      android:drawableTop="@drawable/msg_icon"
      android:gravity="center"
      android:text="9.99w"
      android:textColor="@android:color/white" />

    <TextView
      android:layout_width="50dp"
      android:layout_height="50dp"
      android:layout_marginTop="16dp"
      android:drawableTop="@drawable/share_icon"
      android:gravity="center"
      android:text="6.66w"
      android:textColor="@android:color/white" />
  </LinearLayout>

  <de.hdodenhof.circleimageview.CircleImageView
    android:layout_width="60dp"
    android:layout_height="60dp"
    android:layout_alignParentEnd="true"
    android:layout_alignParentBottom="true"
    android:layout_marginRight="10dp"
    android:layout_marginBottom="60dp"
    android:src="@drawable/header"
    app:civ_border_color="@color/color_01"
    app:civ_border_width="12dp" />

  <LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:layout_marginLeft="10dp"
    android:layout_marginBottom="60dp"
    android:orientation="vertical">

    <TextView
      android:id="@+id/mTitle"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:lineSpacingExtra="5dp"
      android:textColor="@android:color/white"
      android:textSize="16sp"
      tools:text="测试测试数据哈哈哈哈\n家里几个垃圾了个两个垃圾" />

    <LinearLayout
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_marginTop="10dp"
      android:orientation="horizontal"
      android:gravity="center_vertical">

      <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/icon_douyin" />

      <TextView
        android:id="@+id/mMarquee"
        android:layout_width="100dp"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dp"
        android:textColor="@android:color/white"
        android:singleLine="true"
        android:ellipsize="marquee"
        android:marqueeRepeatLimit="marquee_forever"
        android:focusable="true"
        android:focusableInTouchMode="true"
        android:textSize="14sp"/>
    </LinearLayout>
  </LinearLayout>

  <ImageView
    android:id="@+id/mPlay"
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:layout_centerInParent="true"
    android:alpha="0"
    android:clickable="true"
    android:focusable="true"
    android:src="@drawable/play_arrow" />
</RelativeLayout>

1.3、列表Item适配器

然后创建列表Item的适配器TiktokAdapter.java:这里视频封面图片我是自己弄了两张图片,效果看上去不大好,有更好的方案的可以留言一起探讨哦!

public class TiktokAdapter extends RecyclerView.Adapter<TiktokAdapter.ViewHolder> {
  private int[] videos = {R.raw.v1, R.raw.v2};
  private int[] imgs = {R.drawable.fm1, R.drawable.fm2};
  private List<String> mTitles = new ArrayList<>();
  private List<String> mMarqueeList = new ArrayList<>();
  private Context mContext;

  public TiktokAdapter(Context context) {
    this.mContext = context;
    mTitles.add("@乔布奇\nAndroid仿抖音主界面UI效果,\n一起来学习Android开发啊啊啊啊啊\n#Android高级UIAndroid开发");
    mTitles.add("@乔布奇\nAndroid RecyclerView自定义\nLayoutManager的使用方式,仿抖音效果哦");
    mMarqueeList.add("哈哈创作的原声-乔布奇");
    mMarqueeList.add("嘿嘿创作的原声-Jarchie");
  }

  @NonNull
  @Override
  public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
    return new ViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item_tiktok_layout, viewGroup, false));
  }

  @Override
  public void onBindViewHolder(@NonNull final ViewHolder holder, int pos) {
    //第一种方式:获取视频第一帧作为封面图片
//    MediaMetadataRetriever media = new MediaMetadataRetriever();
//    media.setDataSource(mContext,Uri.parse("android.resource://" + mContext.getPackageName() + "/" + videos[pos % 2]));
//    holder.mThumb.setImageBitmap(media.getFrameAtTime());
    //第二种方式:使用固定图片作为封面图片
    holder.mThumb.setImageResource(imgs[pos % 2]);
    holder.mVideoView.setVideoURI(Uri.parse("android.resource://" + mContext.getPackageName() + "/" + videos[pos % 2]));
    holder.mTitle.setText(mTitles.get(pos % 2));
    holder.mMarquee.setText(mMarqueeList.get(pos % 2));
    holder.mMarquee.setSelected(true);
  }

  @Override
  public int getItemCount() {
    return Integer.MAX_VALUE;
  }

  public class ViewHolder extends RecyclerView.ViewHolder {
    RelativeLayout mRootView;
    ImageView mThumb;
    ImageView mPlay;
    TextView mTitle;
    TextView mMarquee;
    CusVideoView mVideoView;

    public ViewHolder(@NonNull View itemView) {
      super(itemView);
      mRootView = itemView.findViewById(R.id.mRootView);
      mThumb = itemView.findViewById(R.id.mThumb);
      mPlay = itemView.findViewById(R.id.mPlay);
      mVideoView = itemView.findViewById(R.id.mVideoView);
      mTitle = itemView.findViewById(R.id.mTitle);
      mMarquee = itemView.findViewById(R.id.mMarquee);
    }
  }

}

二、自定义LayoutManager

我们使用RecyclerView都知道哈,要想让RecylcerView正常工作必须要有两个东西:①、Adapter,负责界面显示适配的,这里我们已经弄好了;②、LayoutManager,告诉列表如何摆放,所以现在想要实现抖音的列表效果的关键就在于这个LayoutManager了,并且普通的LinearLayoutManager是不行的,我们需要自己去实现这个LayoutManger。这里我们取个巧,直接继承LinearLayoutManager来实现自定义布局管理器。

RecyclerView里面有这样一个接口:下面是这个接口的系统源码

//这两个方法不是成对出现的,也没有顺序
public interface OnChildAttachStateChangeListener {
  void onChildViewAttachedToWindow(@NonNull View var1);
  void onChildViewDetachedFromWindow(@NonNull View var1);
}

它里面有两个方法,可以监听列表的Item添加进来和移除出去的两个动作,这是不是就很符合我们现在的使用场景啊,我们的每一屏只有一个Item,并且要在它被添加进来的时候播放视频,在移除时释放掉,所以我们需要实现这个接口。

需要注意的是,这个接口必须在LayoutManager成功进行初始化之后才能监听,所以我们在LayoutManager中重写onAttachedToWindow()方法,在它里面添加这个接口的监听:

@Override
  public void onAttachedToWindow(RecyclerView view) {
    view.addOnChildAttachStateChangeListener(this);
    super.onAttachedToWindow(view);
  }

完了之后呢,会重写接口中的两个方法,在这两个方法里面我们就可以来实现播放和暂停的操作了。那么这里问题又来了,播放和暂停这两个动作都涉及到一个问题,你是播放上一个视频还是播放下一个视频,因为列表是可以往下滑也可以往上滑的啊,所以我们还得重写另一个监听位移变化的方法:scrollVerticallyBy(),这里dy的值为正数是往上滑,负数是往下滑

private int mDrift;//位移,用来判断移动方向
 @Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
  this.mDrift = dy;
  return super.scrollVerticallyBy(dy, recycler, state);
}

OK,这样我们就可以判断是向上滑还是向下滑了,那么上面onChildViewAttachedToWindow()这两个方法就可以写了,在这两个方法中,我们需要把具体的业务逻辑回调到Activity里面去处理,所以这里我们还需要再自定义一个接口OnPageSlideListener :

public interface OnPageSlideListener {
  //释放的监听
  void onPageRelease(boolean isNext, int position);

  //选中的监听以及判断是否滑动到底部
  void onPageSelected(int position, boolean isBottom);
}

现在来处理上面的两个回调接口onChildViewAttachedToWindow()和onChildViewDetachedFromWindow():

 private OnPageSlideListener mOnPageSlideListener;  

  //Item添加进来
  @Override
  public void onChildViewAttachedToWindow(@NonNull View view) {
    //播放视频操作,判断将要播放的是上一个视频,还是下一个视频
    if (mDrift > 0) { //向上
      if (mOnPageSlideListener != null)
        mOnPageSlideListener.onPageSelected(getPosition(view), true);
    } else { //向下
      if (mOnPageSlideListener != null)
        mOnPageSlideListener.onPageSelected(getPosition(view), false);
    }
  }

  //Item移除出去
  @Override
  public void onChildViewDetachedFromWindow(@NonNull View view) {
    //暂停播放操作
    if (mDrift >= 0) {
      if (mOnPageSlideListener != null)
        mOnPageSlideListener.onPageRelease(true, getPosition(view));
    } else {
      if (mOnPageSlideListener != null)
        mOnPageSlideListener.onPageRelease(false, getPosition(view));
    }
  }

既然这里是通过接口的方式回调到Activity中实现,所以我们还得给它设置一个接口:

//接口注入
public void setOnPageSlideListener(OnPageSlideListener mOnViewPagerListener) {
  this.mOnPageSlideListener = mOnViewPagerListener;
}  

写到这里,当然还不行,此时如果你去跑你的项目,你会发现它还是会像普通的RecyclerView一样随意的滑动,所以我们还需要一个类的帮助才行:PagerSnapHelper,它可以实现让RecyclerView像ViewPager一样的滑动效果,这里我们给它绑定上RecyclerView:

 private PagerSnapHelper mPagerSnapHelper;

  public CustomLayoutManager(Context context, int orientation, boolean reverseLayout) {
    super(context, orientation, reverseLayout);
    mPagerSnapHelper = new PagerSnapHelper();
  }

  @Override
  public void onAttachedToWindow(RecyclerView view) {
    view.addOnChildAttachStateChangeListener(this);
    mPagerSnapHelper.attachToRecyclerView(view);
    super.onAttachedToWindow(view);
  }

推荐阅读:让你明明白白的使用RecyclerView——SnapHelper详解

到这里已经可以实现类似ViewPager滑动的效果了,但是我们还需要重写一个方法,不然的话向下滑动播放的时候会有Bug:因为onChildViewAttachedToWindow()和onChildViewDetachedFromWindow()这两个方法并不是成对出现的,它们二者之间也是没有顺序的,因此这里我们再来监听一下滑动状态的改变:判断已经处理完成即手指抬起时的状态

 @Override
  public void onScrollStateChanged(int state) {
    switch (state) {
      case RecyclerView.SCROLL_STATE_IDLE:
        View view = mPagerSnapHelper.findSnapView(this);//拿到当前进来的View
        int position = getPosition(view);
        if (mOnPageSlideListener != null) {
          mOnPageSlideListener.onPageSelected(position, position == getItemCount() - 1);
        }
        break;
    }
  }

CustomLayoutManager完整代码如下:

public class CustomLayoutManager extends LinearLayoutManager implements RecyclerView.OnChildAttachStateChangeListener {
  private int mDrift;//位移,用来判断移动方向

  private PagerSnapHelper mPagerSnapHelper;
  private OnPageSlideListener mOnPageSlideListener;

  public CustomLayoutManager(Context context) {
    super(context);
  }

  public CustomLayoutManager(Context context, int orientation, boolean reverseLayout) {
    super(context, orientation, reverseLayout);
    mPagerSnapHelper = new PagerSnapHelper();
  }

  @Override
  public void onAttachedToWindow(RecyclerView view) {
    view.addOnChildAttachStateChangeListener(this);
    mPagerSnapHelper.attachToRecyclerView(view);
    super.onAttachedToWindow(view);
  }

  //Item添加进来
  @Override
  public void onChildViewAttachedToWindow(@NonNull View view) {
    //播放视频操作,判断将要播放的是上一个视频,还是下一个视频
    if (mDrift > 0) { //向上
      if (mOnPageSlideListener != null)
        mOnPageSlideListener.onPageSelected(getPosition(view), true);
    } else { //向下
      if (mOnPageSlideListener != null)
        mOnPageSlideListener.onPageSelected(getPosition(view), false);
    }
  }

  //Item移除出去
  @Override
  public void onChildViewDetachedFromWindow(@NonNull View view) {
    //暂停播放操作
    if (mDrift >= 0) {
      if (mOnPageSlideListener != null)
        mOnPageSlideListener.onPageRelease(true, getPosition(view));
    } else {
      if (mOnPageSlideListener != null)
        mOnPageSlideListener.onPageRelease(false, getPosition(view));
    }
  }

  @Override
  public void onScrollStateChanged(int state) { //滑动状态监听
    switch (state) {
      case RecyclerView.SCROLL_STATE_IDLE:
        View view = mPagerSnapHelper.findSnapView(this);
        int position = getPosition(view);
        if (mOnPageSlideListener != null) {
          mOnPageSlideListener.onPageSelected(position, position == getItemCount() - 1);
        }
        break;
    }
  }

  @Override
  public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
    this.mDrift = dy;
    return super.scrollVerticallyBy(dy, recycler, state);
  }

  //接口注入
  public void setOnPageSlideListener(OnPageSlideListener mOnViewPagerListener) {
    this.mOnPageSlideListener = mOnViewPagerListener;
  }
}

三、实现播放

我们接着在Activity中实现播放和停止的方法:

//播放
  private void playVideo() {
    View itemView = mRecycler.getChildAt(0);
    final CusVideoView mVideoView = itemView.findViewById(R.id.mVideoView);
    final ImageView mPlay = itemView.findViewById(R.id.mPlay);
    final ImageView mThumb = itemView.findViewById(R.id.mThumb);
    final MediaPlayer[] mMediaPlayer = new MediaPlayer[1];
    mVideoView.start();

    mVideoView.setOnInfoListener(new MediaPlayer.OnInfoListener() {
      @Override
      public boolean onInfo(MediaPlayer mp, int what, int extra) {
        mMediaPlayer[0] = mp;
        mp.setLooping(true);
        mThumb.animate().alpha(0).setDuration(200).start();
        return false;
      }
    });

    //暂停控制
    mPlay.setOnClickListener(new View.OnClickListener() {
      boolean isPlaying = true;

      @Override
      public void onClick(View v) {
        if (mVideoView.isPlaying()) {
          mPlay.animate().alpha(1f).start();
          mVideoView.pause();
          isPlaying = false;
        } else {
          mPlay.animate().alpha(0f).start();
          mVideoView.start();
          isPlaying = true;
        }
      }
    });
  }

  //释放
  private void releaseVideo(int index) {
    View itemView = mRecycler.getChildAt(index);
    final CusVideoView mVideoView = itemView.findViewById(R.id.mVideoView);
    final ImageView mThumb = itemView.findViewById(R.id.mThumb);
    final ImageView mPlay = itemView.findViewById(R.id.mPlay);
    mVideoView.stopPlayback();
    mThumb.animate().alpha(1).start();
    mPlay.animate().alpha(0f).start();
  }

然后处理LayoutManager中回调到Activity中的播放逻辑:

mLayoutManager.setOnPageSlideListener(new OnPageSlideListener() {

      @Override
      public void onPageRelease(boolean isNext, int position) {
        int index;
        if (isNext) {
          index = 0;
        } else {
          index = 1;
        }
        releaseVideo(index);
      }

      @Override
      public void onPageSelected(int position, boolean isNext) {
        playVideo();
      }
    });

Activity的完整代码如下:

public class TikTokIndexActivity extends AppCompatActivity {
  private static final String TAG = TikTokIndexActivity.class.getSimpleName();
  private RecyclerView mRecycler;
  private CustomLayoutManager mLayoutManager;

  @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_tiktok_layout);
    initView();
    initListener();
  }

  //初始化监听
  private void initListener() {
    mLayoutManager.setOnPageSlideListener(new OnPageSlideListener() {

      @Override
      public void onPageRelease(boolean isNext, int position) {
        int index;
        if (isNext) {
          index = 0;
        } else {
          index = 1;
        }
        releaseVideo(index);
      }

      @Override
      public void onPageSelected(int position, boolean isNext) {
        playVideo();
      }
    });
  }

  //初始化View
  private void initView() {
    mRecycler = findViewById(R.id.mRecycler);
    mLayoutManager = new CustomLayoutManager(this, OrientationHelper.VERTICAL, false);
    TiktokAdapter mAdapter = new TiktokAdapter(this);
    mRecycler.setLayoutManager(mLayoutManager);
    mRecycler.setAdapter(mAdapter);
  }

  //播放
  private void playVideo() {
    //...这里的代码见上方说明
  }

  //释放
  private void releaseVideo(int index) {
    //...这里的代码见上方说明
  }
}

到这里,仿抖音首页播放的效果就简单实现了,OK,咱们下期再会吧!

祝:工作顺利!

到此这篇关于Android仿抖音主页效果实现的文章就介绍到这了,更多相关Android抖音主页内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Android仿抖音列表效果

    本文实例为大家分享了Android仿抖音列表效果的具体代码,供大家参考,具体内容如下 当下抖音非常火热,是不是也很心动做一个类似的app吗? 那我们就用RecyclerView实现这个功能吧,关于内存的回收利用就交给RecyclerView就好了. 首先我们先说3个和视频播放暂停相关的接口 public interface OnViewPagerListener { /** * 初始化 */ void onInitComplete(View view); /** * 释放 */ void onP

  • Android 仿抖音的评论列表的UI和效果的实现代码

    抖音是一款音乐创意短视频社交软件,是一个专注年轻人的15秒音乐短视频社区.用户可以通过这款软件选择歌曲,拍摄15秒的音乐短视频,形成自己的作品.此App已在Android各大应用商店和APP Store均有上线. 在design包里面 有一个 BottomSheetDialogFragment 这个Fragment,他已经帮我们处理好了手势,所以实现起来很简单.下面是代码: public class ItemListDialogFragment extends BottomSheetDialog

  • Android 之BottomsheetDialogFragment仿抖音评论底部弹出对话框效果(实例代码)

    实现的效果图: 自定义Fragment继承BottomSheetDialogFragment 重写它的三个方法: onCreateDialog() onCreateView() onStart() 他们的执行顺序是从上到下 import android.app.Dialog; import android.content.Context; import android.graphics.Color; import android.graphics.drawable.ColorDrawable;

  • Android自定义view实现仿抖音点赞效果

    前言 学习自定义view,想找点东西耍一下,刚好看到抖音的点赞效果不错,尝试一下. 抖音效果: 话不多说,先上代码: public class Love extends RelativeLayout { private Context mContext; float[] num = {-30, -20, 0, 20, 30};//随机心形图片角度 public Love(Context context) { super(context); initView(context); } public

  • Android 使用SwipeRefreshLayout控件仿抖音做的视频下拉刷新效果

    SwipeRefreshLayout(这个控件),我先跟大家介绍一下这个控件: 一.SwipeRefreshLayout简单介绍 •先看以下官方文档,已有了很详细的描述了. 官方文档说明 •这里我再大概解释一下: •在竖直滑动时想要刷新页面可以用SwipeRefreshLayout来实现.它通过设置OnRefreshListener来监听界面的滑动从而实现刷新.也可以通过一些方法来设置SwipeRefreshLayout是否可以刷新.如:setRefreshing(true),展开刷新动画. s

  • Android仿抖音主页效果实现代码

    写在前面 各位老铁,我又来啦!既然来了,那肯定又来搞事情啦,话不多说,先上图! "抖音"都玩过吧,是不是很好玩,我反正是天天刷,作为一个非著名的Android低级攻城狮,虽然技术菜的一匹,但是也经常刷着刷着会思考:咦?这玩意是用哪个控件做的?这个效果是咋实现的啊?由于本人技术水平有限,所以今天咱就先挑个比较简单的来看看是如何实现的,思考再三,我们就拿抖音首页的这个效果来练练手吧,话不多说,开搞! 一.准备工作 我们先不急着写代码,先对抖音的这种效果做一个简单的分析,首先需要明确的是它是

  • Android仿抖音右滑清屏左滑列表功能的实现代码

    概述 ​ 项目中要实现仿抖音直播间滑动清屏,侧滑列表的功能,在此记录下实现过程和踩坑记录希望避免大家走些弯路,也当作自己的一个总结 ​ 首先看下Demo中的效果 ​ 阅读文章需要提前熟悉些事件分发的内容,相信大家都已经了解过了,网上也有很多优秀的文章,这里推荐两篇自己读过印象较深的文章 https://www.jb51.net/article/124249.htm https://www.jb51.net/article/124861.htm 关于这方面的知识,在Android中是再重要不过的了

  • Android仿抖音上下滑动布局

    抖音上下滑动,监听播放,自动吸顶,吸底效果,供大家参考,具体内容如下 使用RecyclerView+PagerSnapHelper实现 public class DouYinLayoutManager extends LinearLayoutManager implements RecyclerView.OnChildAttachStateChangeListener{ //判断是否上滑还是下滑 private int mDrift; private OnViewPagerListener on

  • android仿音悦台页面交互效果实例代码

    概述 新版的音悦台 APP 播放页面交互非常有意思,可以把播放器往下拖动,然后在底部悬浮一个小框,还可以左右拖动,然后回弹的时候也会有相应的效果,这种交互效果在头条视频和一些专注于视频的app也是很常见的. 前几天看网友有仿这个 效果,觉得不错,现在分享出来,代码可以再优化,这里的播放器使用的是B站的ijkplayer,先上两张动图. 当图片到达底部后,左右拖动 实现的思路 首先,要是拖动视图缩小的效果,我们肯定需要自定义一个View,而根据我们项目的场景我们这里需要两个View,一个是拖动的V

  • Android仿美团拖拽效果实例代码

    效果图 如上图,实现了拖拽事件的无缝过渡.效果很流畅很自然,之所以写轮子因为实在找不到好用的库,该库参考了https://github.com/woxingxiao/SlidingUpPanelLayout ,其实在大神的开源库里就有Issues提到内嵌 scrollView 时滑动冲突的问题.再加上最近项目里面的详情页就有这样的拖拽效果需求,只好自己实现一遍. 在实现的过程中,就遇到几个比较棘手的问题,也经过了一番挣扎才想出解决的方案. 困难 拖拽释放的时机,如下拉1/6就自动收缩否则回弹,上

  • 微信小程序仿抖音短视频切换效果的实例代码

    一直以为抖音短视频切换假如用小程序做的话应该是比较简单的,直接用swiper实现就好,但在实际写的过程中才发现没那么简单,要控制的逻辑还是挺多的. 还是先看效果 体验路径 自定义组件系列>>仿抖音短视频切换 代码逻辑 直接调用自定义的swiper组件就好 调用代码 js const videoList = [] Page({ data: { videoList, activeId:2, isPlaying:true }, onLoad() { var that = this wx.getSys

  • Android自定义videoview仿抖音界面

    本文实例为大家分享了Android自定义videoview仿抖音界面的具体代码,供大家参考,具体内容如下 1.效果图 和抖音的界面效果一模一样,而且可以自定义,需要什么页面,请自己定义 2.自定义videoview package com.example.myapplication20; import android.content.Context; import android.util.AttributeSet; import android.widget.VideoView; /** *

随机推荐