Android开发之满屏弹幕

2023-11-02 21:10
文章标签 android 开发 弹幕 满屏

本文主要是介绍Android开发之满屏弹幕,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

在开发弹幕之前我也搜索了很多文章来借鉴,但由于太多布局都不是自己想要的,而且相关引入也无法使用,在最后决定开发自定义弹幕。在网上搜索了大量的自定义布局,B站的弹幕也有很多大佬扒出来使用,到最后我在种种因素下开发出来简陋的自定义弹幕。能满足相关需求。

一.弹幕实体类

一般来说弹幕会有头像,昵称,评论内容,点赞数组成,当然有的设计会没有昵称而是显示定位内容。因此我们需要一个相关的数据类去存储弹幕的数据,因为弹幕肯定不止是一条啦~
以下是相关的弹幕实体类定义。

public class Danmu{private long id;//弹幕idprivate String headerUrl;//头像private String userContent;//内容private String userLocation;//定位private String userLike;//点赞数private String userId;//用户idprivate boolean isFirst;//用户是否第一次点击点赞,第二次点击取消点赞public long getId() {return id;}public void setId(long id) {this.id = id;}public String getHeaderUrl() {return headerUrl;}public void setHeaderUrl(String headerUrl) {this.headerUrl = headerUrl;}public String getUserContent() {return userContent;}public void setUserContent(String userContent) {this.userContent = userContent;}public String getUserLocation() {return userLocation;}public void setUserLocation(String userLocation) {this.userLocation = userLocation;}public String getUserLike() {return userLike;}public void setUserLike(String userLike) {this.userLike = userLike;}public String getUserId() {return userId;}public void setUserId(String userId) {this.userId = userId;}public boolean isFirst() {return isFirst;}public void setFirst(boolean first) {isFirst = first;}}

二.自定义弹幕view

因为开发的是一个自定义的弹幕,所以说相关的布局都是由自己所编写了,因此在这里我们需要自定义一个弹幕view去满足自己的需求。
该弹幕布局由一个圆形头像CircleImageView,评价内容TextView,定位内容TextView以及点赞按钮组成,在这里惯性使用了TextView来编写这个点赞按钮,布局类的可以根据自己的需求来编写,不需要完全保持一致。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/rl_chat"android:layout_width="@dimen/dp_300"android:layout_height="wrap_content"xmlns:app="http://schemas.android.com/apk/res-auto"android:background="@drawable/ig_snztzdms12"><de.hdodenhof.circleimageview.CircleImageViewandroid:id="@+id/iv_user_icon"android:layout_width="@dimen/dp_48"android:layout_height="@dimen/dp_48"android:layout_marginStart="@dimen/dp_32"android:layout_marginTop="@dimen/dp_5"app:civ_border_color="@color/white"app:civ_border_width="1dp"android:src="@drawable/img_exam_ave_time_rocket" /><TextViewandroid:id="@+id/tv_user_content"android:textSize="18sp"android:textColor="#13113A"android:text="这也太难了ba bhhhh"android:maxWidth="@dimen/dp_120"android:ellipsize="end"android:singleLine="true"android:layout_marginTop="5dp"android:layout_marginStart="8dp"android:layout_toEndOf="@id/iv_user_icon"android:layout_width="wrap_content"android:layout_height="wrap_content"tools:ignore="HardcodedText" /><TextViewandroid:id="@+id/tv_user_location"android:textSize="16sp"android:textColor="#8013113A"android:text="广东省广州市"android:drawablePadding="5dp"android:drawableStart="@drawable/ig_snztzdms11"android:maxWidth="@dimen/dp_120"android:ellipsize="end"android:singleLine="true"android:layout_marginTop="2dp"android:layout_marginStart="8dp"android:layout_below="@id/tv_user_content"android:layout_toEndOf="@id/iv_user_icon"android:layout_width="wrap_content"android:layout_height="wrap_content"tools:ignore="HardcodedText,UseCompatTextViewDrawableXml" /><TextViewandroid:id="@+id/tv_user_like"android:text="认同"android:textColor="#FF7F5D"android:textSize="20sp"android:textStyle="bold"android:paddingTop="4dp"android:paddingBottom="4dp"android:paddingStart="10dp"android:paddingEnd="10dp"android:drawableStart="@drawable/ig_snztzdms10"android:layout_alignParentEnd="true"android:layout_marginTop="14dp"android:layout_marginEnd="@dimen/dp_14"android:background="@drawable/bg_item_like_nor"android:layout_width="wrap_content"android:layout_height="wrap_content"tools:ignore="HardcodedText,UseCompatTextViewDrawableXml" /></RelativeLayout>

在build.gradle(app)中引入该插件,即可使用上述布局中的圆形布局,还可带边框

implementation ‘de.hdodenhof:circleimageview:3.1.0’

在编写完布局文件之后,需要进一步开发自定义弹幕view,形成一个组件,可在项目中任何位置中调用。其实这个弹幕组件它的一个动起来的过程就是一个属性动画ValueAnimator刷起来的过程,因此它就是ValueAnimator+handler去异步加载每一个弹幕view显示到页面上,由于弹幕是一个接着一个被加载到页面上的 ,对于用户来说它就是在屏幕上飘了出来,也就是我们平时看视频时能经常看到的满屏加载的弹幕。在这里是设置了弹幕自动循环播放的,每一个弹幕间隔时长也有几秒钟,是为了可以点击弹幕事件而做的处理,因为有时候这样刷弹幕出来,刷新过快会导致弹幕速度过快,对于用户体验来说它就像是卡bug了一样,唰一下就没了,都来不及看清弹幕内容是什么;另外加载速度过快会导致数据量不够的情况下,很容易造成应用闪退,用户体验感极差。


import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import android.widget.TextView;import com.bumptech.glide.RequestManager;import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;import de.hdodenhof.circleimageview.CircleImageView;public class DanmuView extends FrameLayout {private final String TAG = "DanmuView";private static final long DEFAULT_ANIM_DURATION = 18000; //默认每个动画的播放时长private static final long DEFAULT_QUERY_DURATION = 3000; //遍历弹幕的默认间隔private List<View> mViews = new ArrayList<>();//弹幕队列//记录当前仍在显示状态的弹幕的垂直方向位置(避免重复)private Set existMarginValues = new HashSet();private int nowIndex = 0;//下标private int mWidth;//弹幕的宽度private int mHeight;//弹幕的高度private boolean TopDirectionFixed;//弹幕顶部的方向是否固定private int mTopGravity = Gravity.CENTER_VERTICAL;//顶部方向固定时的默认对齐方式private RequestManager mGlide;//用于异步加载头像数据private OnClickListener mListener;//点击事件的监听private boolean IS_SHOW = true;//判断弹幕是否显示,默认是显示private final int TIP_SHOW_DANMU = 0x100;//显示弹幕private final int TIP_HIDE_DANMU = 0x101;//隐藏弹幕public void setHeight(int height) {mHeight = height;}public void setWidth(int width) {mWidth = width;}public void setTopGravity(int gravity) {this.mTopGravity = gravity;}public void add(List<Danmu> danmuList, RequestManager glide) {this.mGlide = glide;//初始化下数据mViews = new ArrayList<>();existMarginValues = new HashSet<>();for (int i = 0; i < danmuList.size(); i++) {Danmu danmu = (Danmu) danmuList.get(i);addDanmuToQueue(danmu);}}public void add(Danmu danmu) {addDanmuToQueue(danmu);}public DanmuView(Context context) {this(context, null);}public DanmuView(Context context, AttributeSet attrs) {this(context, attrs, 0);}public DanmuView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}/**根据弹幕多少调整下刷新的时间* 尤其是弹幕数据少的时候;不然的话会造成刷新速度够快、添加弹幕失败,已经存在的弹幕还未结束动画,继续添加该弹幕数据会爆错,导致应用停止运行* addView(view) - The specified child already has a parent. You must call removeView() on the child s parent first.* */private long getDuration() {if (mViews != null) {if (mViews.size() == 1) {return 18300;} else if (mViews.size() == 2) {return 15000;} else if (mViews.size() == 3) {return 10000;} else if (mViews.size() == 4) {return 7000;} else if (mViews.size() == 5) {return 6000;} else if (mViews.size() == 6) {return 5500;} else {return DEFAULT_QUERY_DURATION;}}return DEFAULT_QUERY_DURATION;}@SuppressLint("HandlerLeak")private final Handler handler  = new Handler() {@Overridepublic void handleMessage(Message msg) {if (msg.what == TIP_SHOW_DANMU) {removeMessages(msg.what);if (IS_SHOW) {//循环取出弹幕显示if (mViews.size() > 0) {final View view = (View) mViews.get(nowIndex);if (null != view) {//添加弹幕showDanmu(view);}nowIndex++;if (nowIndex == mViews.size()) {nowIndex = 0;}long time = getDuration();sendEmptyMessageDelayed(TIP_SHOW_DANMU, time);}}} else if (msg.what == TIP_HIDE_DANMU) {removeMessages(msg.what);nowIndex = 0;}}};/*** 将要展示的弹幕添加到队列中* @param danmu 弹幕数据*/@SuppressLint("SetTextI18n")private void addDanmuToQueue(Danmu danmu) {if (null != danmu) {final View layout = View.inflate(getContext(), R.layout.layout_item_silde_content, null);//设置内容TextView userContent = layout.findViewById(R.id.tv_user_content);userContent.setText(danmu.getUserContent());//设置定位TextView userLocation = layout.findViewById(R.id.tv_user_location);String text = danmu.getUserLocation().isEmpty() ? "广东省" : danmu.getUserLocation();userLocation.setText(text);//设置点赞数TextView userLike = layout.findViewById(R.id.tv_user_like);if (danmu.getUserLike().equals("认同")) {userLike.setText(danmu.getUserLike());} else {userLike.setText("x" + danmu.getUserLike());}String uid = BaseUtil.getUid() + "";if (danmu.getUserId().equals(uid)) {layout.setBackgroundResource(R.drawable.ig_snztzdms09);userLike.setBackgroundResource(R.drawable.bg_item_like_ser);} else {layout.setBackgroundResource(R.drawable.ig_snztzdms12);userLike.setBackgroundResource(R.drawable.bg_item_like_nor);}//设置图片CircleImageView userIcon = layout.findViewById(R.id.iv_user_icon);LoadImage(mGlide, danmu.getHeaderUrl(), userIcon);layout.setOnClickListener(view -> {if (danmu.isFirst()) {mListener.onItemClick(danmu.getId(), 1);if (!userLike.getText().equals("认同")) {int num = Integer.parseInt(danmu.getUserLike());userLike.setText("x" + (num+1));} else {userLike.setText("x1");}danmu.setFirst(false);} else {mListener.onItemClick(danmu.getId(), -1);if (!userLike.getText().equals("x1")) {userLike.setText("x" + danmu.getUserLike());} else {userLike.setText("认同");}danmu.setFirst(true);}} );layout.measure(0, 0);//添加弹幕到队列中mViews.add(layout);}}public interface OnClickListener {/**点击弹幕*/void onItemClick(long id, int status);}//实现这个View的监听器public void setOnClickListener(OnClickListener listener){this.mListener = listener;   //引用监听器类对象,在这里可以使用监听器类的对象}/*** 播放弹幕* @param topDirectionFixed 弹幕顶部的方向是否固定*/public void startPlay(boolean topDirectionFixed) {this.TopDirectionFixed = topDirectionFixed;if (mWidth == 0 || mHeight == 0) {getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {@SuppressLint("NewApi")@Overridepublic void onGlobalLayout() {getViewTreeObserver().removeOnGlobalLayoutListener(this);if (mWidth == 0) mWidth = getWidth() - getPaddingLeft() - getPaddingRight();if (mHeight == 0) mHeight = getHeight() - getPaddingTop() - getPaddingBottom();if (mViews.size() > 0) {handler.sendEmptyMessage(TIP_SHOW_DANMU);}}});} else {if (mViews.size() > 0) {handler.sendEmptyMessage(TIP_SHOW_DANMU);}}}/*** 显示弹幕,包括动画的执行* @param view view*/@SuppressLint("RtlHardcoded")private void showDanmu(final View view) {Log.d(TAG, "mWidth:" + mWidth + " mHeight:" + mHeight);final LayoutParams lp = new LayoutParams(view.getMeasuredWidth(), view.getMeasuredHeight());lp.leftMargin = mWidth;if (TopDirectionFixed) {lp.gravity = mTopGravity | Gravity.LEFT;} else {lp.gravity = Gravity.LEFT | Gravity.TOP;lp.topMargin = getRandomTopMargin(view);}view.setLayoutParams(lp);view.setTag(lp.topMargin);//设置item水平滚动的动画ValueAnimator animator = ValueAnimator.ofInt(mWidth, -view.getMeasuredWidth());animator.addUpdateListener(animation -> {lp.leftMargin = (int) animation.getAnimatedValue();view.setLayoutParams(lp);});addView(view);//显示弹幕animator.setDuration(DEFAULT_ANIM_DURATION);animator.start();//开启动画animator.addListener(new AnimatorListenerAdapter() {@Overridepublic void onAnimationEnd(Animator animation) {view.clearAnimation();existMarginValues.remove(view.getTag());//移除已使用过的顶部边距removeView(view);//移除弹幕animation.cancel();}});}private int getRandomTopMargin(View view) {//计算可用的行数int linesCount = mHeight / view.getMeasuredHeight();if (linesCount <= 1) {linesCount = 1;}Log.d(TAG, "linesCount:" + linesCount);//检查重叠while (true) {int randomIndex = (int) (Math.random() * linesCount);int marginValue = randomIndex * (mHeight / linesCount);//边界检查int range = 10;if (marginValue > mHeight - view.getMeasuredHeight()) {marginValue = mHeight - view.getMeasuredHeight() - range;}if (marginValue == 0) {marginValue = range;}if (!existMarginValues.contains(marginValue)) {existMarginValues.add(marginValue);Log.d(TAG, "marginValue:" + marginValue);return marginValue;}}}/**
*暴露给外面调用-可隐藏或者是显示弹幕 ,达到一键开启的效果
*/public void setShow(boolean IS_SHOW) {this.IS_SHOW = IS_SHOW;if (IS_SHOW) {handler.removeMessages(TIP_HIDE_DANMU);handler.removeMessages(TIP_SHOW_DANMU);handler.sendEmptyMessage(TIP_SHOW_DANMU);} else {handler.removeMessages(TIP_SHOW_DANMU);handler.removeMessages(TIP_HIDE_DANMU);handler.sendEmptyMessage(TIP_HIDE_DANMU);}}/**判断下是否有视图*/public boolean hasViews() {if (mViews != null) {return mViews.size() > 0;}return false;}/**释放线程资源,防止占用内存 -- 暴露给其他页面调用释放资源*/public void removeHandler() {handler.removeCallbacksAndMessages(null);}/*** 使用glide加载图片,避免出现Android Glide You cannot start a load for a destroyed activity 的异常,其原因是由于Activity/Fragment 已经 destroy,而程序代码中依然在使用 Glide 加载图片导致的* @param glide RequestManager 是帮助管理生命周期的,使得 Glide 的生命周期与 Activity/Fragment 保持同步,如果 Activity/Fragment 销毁,相关 Glide 加载也会进行销毁,从而达到不浪费内存的目的* @param url 图片url* @param view 加载的视图view*/public void LoadImage(RequestManager glide, String url, ImageView view) {glide.load(url).placeholder(R.drawable.img_home_head_portrait1)//i避免没有解析到头像.error(R.drawable.img_home_head_portrait1).into(view);}
}

三.相关调用

    <DanmuViewandroid:id="@+id/layout_danmu"android:visibility="gone"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/transparent"/>
//加载弹幕数据到页面上 -- 初始化弹幕if (data != null && data.size() > 0) {ArrayList<Danmu> danmuList = new ArrayList<>();for (int i = 0; i < data.size(); i++) {AppPaperChatDetails detail = data.get(i);Danmu danmu = new Danmu();danmu.setId(detail.getId());danmu.setUserId(detail.getUid()+"");danmu.setHeaderUrl(detail.getAvatar());danmu.setUserContent(detail.getAssess());if (detail.getAssessLike() == 0) {danmu.setUserLike("认同");} else {danmu.setUserLike(""+detail.getAssessLike());}danmu.setUserLocation(detail.getProvince());danmu.setFirst(true);danmuList.add(danmu);}mBinding.layoutDanmu.add(danmuList, Glide.with(this));mBinding.layoutDanmu.setShow(true);mBinding.layoutDanmu.startPlay(false);mBinding.layoutDanmu.setOnClickListener((id, status) -> {if (id != 0) {mPresenter.postBarrageStatus(id, status);//点赞}});mBinding.layoutDanmu.setVisibility(View.VISIBLE);} else {mBinding.layoutDanmu.setVisibility(View.GONE);}//一键开关弹幕if (mBinding.tvChatClose.getVisibility() == View.VISIBLE) {if (mBinding.layoutDanmu.hasViews()) {mBinding.layoutDanmu.setShow(false);if (mBinding.layoutDanmu.getChildCount() > 0) {mBinding.layoutDanmu.removeAllViews();}mBinding.layoutDanmu.setVisibility(View.GONE);mBinding.tvChatClose.setVisibility(View.GONE);mBinding.tvChatOpen.setVisibility(View.VISIBLE);} else {Toast.makeText(this, "暂无弹幕哦~", Toast.LENGTH_SHORT).show();}} else {if (mBinding.layoutDanmu.hasViews()) {mBinding.layoutDanmu.setShow(true);mBinding.layoutDanmu.setVisibility(View.VISIBLE);mBinding.tvChatClose.setVisibility(View.VISIBLE);mBinding.tvChatOpen.setVisibility(View.GONE);} else {Toast.makeText(this, "暂无弹幕哦~", Toast.LENGTH_SHORT).show();}}

效果图
大体框架就是如此,感兴趣的可以去操作试一试。
有些内容

后序

这样一个简单的自定义弹幕就开发完成了,基本上是拿过去就可以使用哦,相关的弹幕刷屏动画时间等,点击事件都可以根据自己的需求去更改,变得更加的多样化。有不足之处,欢迎指正,大家共同进步。

番外篇

弹幕的上下滚动内容

在布局文件中直接使用一个Linearlayout控件。

<!--标签滚动--><LinearLayoutandroid:id="@+id/ll_label_ques"android:layout_alignParentBottom="true"android:layout_alignParentEnd="true"android:layout_width="wrap_content"android:layout_height="wrap_content"android:orientation="vertical"android:layout_gravity="end"android:layout_marginEnd="@dimen/dp_15"android:padding="15dp"android:visibility="visible"android:showDividers="middle"/>

可直接在activity里直接编写内容。


//在handler里刷新页面
if (msg.what == 0) {removeMessages(msg.what);if (isShow) {mBinding.llLabelQues.setVisibility(View.VISIBLE);TextView textView = obtainTextView();mBinding.llLabelQues.addView(textView);sendEmptyMessageDelayed(0, 2000);index++;if (texts != null && index == texts.length) {index = 0;}if (mBinding.llLabelQues.getChildCount() == 3) {mHandler.sendEmptyMessage(1);}}} else if (msg.what == 1) {removeMessages(msg.what);//给展示的第一个view增加渐变透明动画mBinding.llLabelQues.getChildAt(0).animate().alpha(0).setDuration(500).start();sendEmptyMessageDelayed(2, 3000);} else if (msg.what == 2) {removeMessages(msg.what);//删除顶部viewif  (mBinding.llLabelQues.getChildCount() > 0) {mBinding.llLabelQues.removeViewAt(0);}} else if (msg.what == 3) {removeMessages(msg.what);if  (mBinding.llLabelQues.getChildCount() > 0) {mBinding.llLabelQues.removeAllViews();}mBinding.llLabelQues.setVisibility(View.GONE);}//==============================提示的标签信息============================private String[] texts;private int index = 0;private boolean isShow = false;Pools.SimplePool<TextView> textViewSimplePool;private TextView obtainTextView() {if (textViewSimplePool != null) {TextView textView = textViewSimplePool.acquire();if (textView == null) {textView = new TextView(this);textView.setPadding(dp2px(10), dp2px(5), dp2px(10), dp2px(5));textView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) textView.getLayoutParams();lp.setMargins(0, 10, 0, 10);textView.setLayoutParams(lp);textView.setTextColor(Color.parseColor("#FF7F5D"));textView.setMaxLines(1);textView.setEllipsize(TextUtils.TruncateAt.END);textView.setGravity(Gravity.CENTER);textView.setTextSize(15);textView.setBackgroundResource(R.drawable.bg_label);}textView.setText(texts[index]);return textView;}return new TextView(this);}private int dp2px(float dp) {DisplayMetrics displayMetrics = new DisplayMetrics();this.getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, displayMetrics);}@SuppressLint("SetTextI18n")@Overridepublic void initLabelData(T record) {if (record != null) {List<String> arrs = record.getAvatars();if (arrs != null) {int i = 0;for (String url : arrs) {if (i == 0) {LoadImage(Glide.with(this), url, mBinding.ivUserImg1);} else if (i == 1){LoadImage(Glide.with(this), url, mBinding.ivUserImg2);} else if (i == 2) {LoadImage(Glide.with(this), url, mBinding.ivUserImg3);break;}
//                    else if (i == 3) {//弃用
//                        AppTestUtil.loadImage(Glide.with(this), url, mBinding.ivUserImg4);
//                        break;
//                    }i++;}}if (record.getMember() == 0) {Random random = new Random();int num = random.nextInt(100)+1;mBinding.tvUserTip.setText("有"+ num + "人正在挑战");} else {mBinding.tvUserTip.setText("有"+AppTestUtil.toNumber(record.getMember())+"正在挑战");}if (record.getContent() != null && record.getContent().size() > 0) {texts = new String[record.getContent().size()];int i = 0;for (String text : record.getContent()) {if (!text.isEmpty()) {//正则表达式 提取字符串中的数字Pattern pattern = Pattern.compile("\\d+");Matcher matcher = pattern.matcher(text);String result = null;while (matcher.find()) {result = matcher.group(0);}if (!"".equals(result)) {assert result != null;int day = Integer.parseInt(result) / 1440;String date = "";if (day < 1) {int hour = Integer.parseInt(result) / 60;if (hour < 1) {texts[i] = text;} else {date = " " + hour + "小时前";String str1 = text.replaceAll("分钟前", "");texts[i] = str1.replaceAll(result, date);}} else {date = " " + day + "天前";String str1 = text.replaceAll("分钟前", "");texts[i] = str1.replaceAll(result, date);}}i++;}}} else {texts = new String[] {"美美 10分钟前答对了该题", "小明 8分钟前答对了该题", "黄兴雅 6分钟前答对了该题", "凡凡 4分钟前答对了该题", "张馨雅 2分钟前答对了该题"};}textViewSimplePool = new Pools.SimplePool<>(texts.length);transition = new LayoutTransition();//添加动画ObjectAnimator valueAnimator = ObjectAnimator.ofFloat(null, " aloha", 0, 1);valueAnimator.addListener(new Animator.AnimatorListener() {@Overridepublic void onAnimationStart(Animator animator) {//当前展示超过四条,执行删除动画if (mBinding.llLabelQues.getChildCount() == 2) {mHandler.sendEmptyMessage(1);}}@Overridepublic void onAnimationEnd(Animator animator) {if (mBinding.llLabelQues.getChildCount() == 3) {//动画执行完毕,删除viewmHandler.sendEmptyMessage(2);}}@Overridepublic void onAnimationCancel(Animator animator) {}@Overridepublic void onAnimationRepeat(Animator animator) {}});transition.setAnimator(LayoutTransition.APPEARING, valueAnimator);//删除动画PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0, 0);ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(null, new PropertyValuesHolder[]{alpha}).setDuration(transition.getDuration(LayoutTransition.DISAPPEARING));transition.setAnimator(LayoutTransition.DISAPPEARING, objectAnimator);mBinding.llLabelQues.setLayoutTransition(transition);
//            mHandler.sendEmptyMessage(0);}}/*** 使用glide加载图片,避免出现Android Glide You cannot start a load for a destroyed activity 的异常,其原因是由于Activity/Fragment 已经 destroy,而程序代码中依然在使用 Glide 加载图片导致的* @param glide RequestManager 是帮助管理生命周期的,使得 Glide 的生命周期与 Activity/Fragment 保持同步,如果 Activity/Fragment 销毁,相关 Glide 加载也会进行销毁,从而达到不浪费内存的目的* @param url 图片url* @param view 加载的视图view*/public void LoadImage(RequestManager glide, String url, ImageView view) {glide.load(url).placeholder(R.drawable.img_home_head_portrait1)//i避免没有解析到头像.error(R.drawable.img_home_head_portrait1).into(view);}//=====================================================================

这篇关于Android开发之满屏弹幕的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/333316

相关文章

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

Hadoop企业开发案例调优场景

需求 (1)需求:从1G数据中,统计每个单词出现次数。服务器3台,每台配置4G内存,4核CPU,4线程。 (2)需求分析: 1G / 128m = 8个MapTask;1个ReduceTask;1个mrAppMaster 平均每个节点运行10个 / 3台 ≈ 3个任务(4    3    3) HDFS参数调优 (1)修改:hadoop-env.sh export HDFS_NAMENOD

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

活用c4d官方开发文档查询代码

当你问AI助手比如豆包,如何用python禁止掉xpresso标签时候,它会提示到 这时候要用到两个东西。https://developers.maxon.net/论坛搜索和开发文档 比如这里我就在官方找到正确的id描述 然后我就把参数标签换过来

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

Linux_kernel驱动开发11

一、改回nfs方式挂载根文件系统         在产品将要上线之前,需要制作不同类型格式的根文件系统         在产品研发阶段,我们还是需要使用nfs的方式挂载根文件系统         优点:可以直接在上位机中修改文件系统内容,延长EMMC的寿命         【1】重启上位机nfs服务         sudo service nfs-kernel-server resta

【区块链 + 人才服务】区块链集成开发平台 | FISCO BCOS应用案例

随着区块链技术的快速发展,越来越多的企业开始将其应用于实际业务中。然而,区块链技术的专业性使得其集成开发成为一项挑战。针对此,广东中创智慧科技有限公司基于国产开源联盟链 FISCO BCOS 推出了区块链集成开发平台。该平台基于区块链技术,提供一套全面的区块链开发工具和开发环境,支持开发者快速开发和部署区块链应用。此外,该平台还可以提供一套全面的区块链开发教程和文档,帮助开发者快速上手区块链开发。

Vue3项目开发——新闻发布管理系统(六)

文章目录 八、首页设计开发1、页面设计2、登录访问拦截实现3、用户基本信息显示①封装用户基本信息获取接口②用户基本信息存储③用户基本信息调用④用户基本信息动态渲染 4、退出功能实现①注册点击事件②添加退出功能③数据清理 5、代码下载 八、首页设计开发 登录成功后,系统就进入了首页。接下来,也就进行首页的开发了。 1、页面设计 系统页面主要分为三部分,左侧为系统的菜单栏,右侧