本文主要是介绍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开发之满屏弹幕的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!