Android仿直播类app赠送礼物功能

直播界面

实现的是播放本地的视频文件:

/**
 * 直播界面,用于对接直播功能
 */
public class LiveFrag extends Fragment {

 private ImageView img_thumb;
 private VideoView video_view;

 @Nullable
 @Override
 public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
 View view = inflater.inflate(R.layout.frag_live, null);
 img_thumb = view.findViewById(R.id.img_thumb);
 img_thumb.setVisibility(View.GONE);
 video_view = view.findViewById(R.id.video_view);
 video_view.setVisibility(View.VISIBLE);
 video_view.setVideoURI(Uri.parse("android.resource://" + getActivity().getPackageName() + "/" + R.raw.video_1));
 video_view.start();
 video_view.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
 @Override
 public void onCompletion(MediaPlayer mp) {
 video_view.setVideoURI(Uri.parse("android.resource://" + getActivity().getPackageName() + "/" + R.raw.video_1));
 //或 //mVideoView.setVideoPath(Uri.parse(_filePath));
 video_view.start();
 }
 });
 return view;
 }
}

布局文件 frag_live.xml 如下:

<?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="match_parent"
 android:orientation="vertical">
 <VideoView
 android:id="@+id/video_view"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:clickable="false"
 android:focusable="false"
 android:visibility="gone" />
 <ImageView
 android:id="@+id/img_thumb"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:clickable="false"
 android:focusable="false"
 android:scaleType="centerCrop"
 android:src="@mipmap/img_video_1"
 android:visibility="visible" />
</LinearLayout>

滑动隐藏效果

需要实现的效果如下:

自定义DialogFragment,使用ViewPager,第一个为空的Fragment,第二个为我们需要的Fragment,左右滑动来切换显示和隐藏效果。

观众功能交互页面 InteractiveFrag 如下:

/**
 * 观众功能交互页面, 滑动隐藏效果
 */
public class InteractiveFrag extends DialogFragment {

 public View view;
 public Context myContext;
 private ViewPager vp_interactive;
 private LayerFrag layerFrag;

 @Override
 public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
 view = inflater.inflate(R.layout.frag_interactive, null);
 // 初始化
 initView();
 initData();
 return view;
 }

 /**
 * 初始化View
 */
 public void initView() {
 vp_interactive = view.findViewById(R.id.vp_interactive);
 }

 /**
 * 初始化数据
 */
 public void initData() {
 // EmptyFrag:什么都没有
 // LayerFrag:交互界面
 // 这样就达到了滑动隐藏交互的需求
 vp_interactive.setAdapter(new FragmentPagerAdapter(getChildFragmentManager()) {
 @Override
 public int getCount() {
 return 2;
 }

 @Override
 public Fragment getItem(int position) {
 if (position == 0) {
 return new EmptyFrag(); // 返回空界面的fragment
 } else if (position == 1) {
 return layerFrag = new LayerFrag(); // 返回交互界面的frag
 } else { // 设置默认

 return new EmptyFrag();
 }
 }
 });
 // 设置默认显示交互界面
 vp_interactive.setCurrentItem(1);

 // 同时将界面改为resize已达到软键盘弹出时Fragment不会跟随移动
 getDialog().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
 }

 @Override
 public Dialog onCreateDialog(Bundle savedInstanceState) {

 // 设置DialogFragment的样式,这里的代码最好还是用我的,大家不要改动
 Dialog dialog = new Dialog(getActivity(), R.style.MainDialog) {

 @Override
 public void onBackPressed() {
 super.onBackPressed();
 getActivity().finish();
 }
 };
 return dialog;
 }
}

frag_interactive.xml文件如下:

<?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="match_parent"
 android:orientation="vertical" >

 <androidx.viewpager.widget.ViewPager
 android:id="@+id/vp_interactive"
 android:layout_width="match_parent"
 android:layout_height="match_parent" />
</LinearLayout>

用户交互页 LayerFrag:

public class LayerFrag extends Fragment {
 @Nullable
 @Override
 public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
 return inflater.inflate(R.layout.frag_layer, null);
 }
}

frag_layer:

<?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"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:orientation="vertical">
 <LinearLayout
 android:id="@+id/ll_anchor"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:gravity="center_vertical"
 android:orientation="horizontal"
 android:paddingLeft="10dp"
 android:paddingTop="10dp">
 <RelativeLayout
 android:layout_width="wrap_content"
 android:layout_height="wrap_content">
 <LinearLayout
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:layout_centerVertical="true"
 android:background="@drawable/bg_radius_top_black"
 android:gravity="center_vertical"
 android:orientation="vertical"
 android:paddingLeft="55dp"
 android:paddingTop="2dp"
 android:paddingRight="10dp"
 android:paddingBottom="2dp">
 <TextView
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:text="十三妹哦"
 android:textColor="@android:color/white"
 android:textSize="12sp" />
 <LinearLayout
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:gravity="center_vertical"
 android:orientation="horizontal">
 <ImageView
 android:layout_width="35dp"
 android:layout_height="20dp"
 android:src="@drawable/hani_icon_tag_exp" />
 <TextView
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:layout_marginLeft="5dp"
 android:text="17万"
 android:textColor="@android:color/white"
 android:textSize="10sp" />
 </LinearLayout>
 </LinearLayout>
 <com.hongx.zhibo.utils.CircleImageView
 android:id="@+id/lv_anchorIcon"
 android:layout_width="50dp"
 android:layout_height="50dp"
 android:src="@drawable/zf"
 app:border_color="@color/colorWhite"
 app:border_width="1dp" />
 </RelativeLayout>
 <com.hongx.zhibo.utils.HorizontalListView
 android:id="@+id/hlv_audience"
 android:layout_width="match_parent"
 android:layout_height="45dp"
 android:layout_marginLeft="10dp" />
 </LinearLayout>
 <RelativeLayout
 android:id="@+id/rl_num"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:layout_below="@+id/ll_anchor"
 android:layout_marginTop="5dp"
 android:paddingLeft="10dp"
 android:paddingRight="10dp">
 <LinearLayout
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:background="@drawable/bg_radius_bottom_pink"
 android:gravity="center_vertical"
 android:paddingLeft="10dp"
 android:paddingTop="2dp"
 android:paddingRight="10dp"
 android:paddingBottom="2dp">
 <ImageView
 android:layout_width="20dp"
 android:layout_height="10dp"
 android:src="@drawable/molive_icon_charm_lv_20" />
 <TextView
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:layout_marginLeft="5dp"
 android:text="小时榜单第5名"
 android:textColor="#fff"
 android:textSize="10sp" />
 </LinearLayout>
 <TextView
 android:id="@+id/tv_momocode"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:layout_alignParentRight="true"
 android:layout_centerVertical="true"
 android:background="@drawable/bg_radius_top_black"
 android:paddingLeft="10dp"
 android:paddingTop="2dp"
 android:paddingRight="10dp"
 android:paddingBottom="2dp"
 android:text="MoMo: 12345678"
 android:textColor="@android:color/white"
 android:textSize="10sp" />
 </RelativeLayout>
 <LinearLayout
 android:id="@+id/ll_gift_group"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:layout_above="@+id/lv_message"
 android:layout_marginTop="10dp"
 android:layout_marginBottom="10dp"
 android:animateLayoutChanges="true"
 android:gravity="top"
 android:orientation="vertical" />
 <ListView
 android:id="@+id/lv_message"
 android:layout_width="230dp"
 android:layout_height="150dp"
 android:layout_above="@+id/fl_bottom"
 android:layout_marginLeft="10dp"
 android:cacheColorHint="#00000000"
 android:divider="@null"
 android:dividerHeight="5dp"
 android:listSelector="#00000000"
 android:scrollbarStyle="outsideOverlay"
 android:scrollbars="none"
 android:transcriptMode="normal" />
 <FrameLayout
 android:id="@+id/fl_bottom"
 android:layout_width="match_parent"
 android:layout_height="70dp"
 android:layout_alignParentStart="true"
 android:layout_alignParentBottom="true">
 <LinearLayout
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:background="@android:color/transparent"
 android:gravity="center_vertical"
 android:orientation="horizontal"
 android:paddingLeft="10dp"
 android:paddingRight="10dp">
 <Button
 android:id="@+id/tv_chat"
 android:layout_width="40dp"
 android:layout_height="70dp"
 android:gravity="center"
 android:text="聊天"
 android:textColor="#333"
 android:textSize="10sp" />
 <View
 android:layout_width="0dp"
 android:layout_height="1dp"
 android:layout_weight="1" />
 <Button
 android:id="@+id/btn_gift01"
 android:layout_width="40dp"
 android:layout_height="70dp"
 android:layout_marginRight="5dp"
 android:gravity="center"
 android:text="送香皂"
 android:textColor="#333"
 android:textSize="12sp" />
 <Button
 android:id="@+id/btn_gift02"
 android:layout_width="40dp"
 android:layout_height="70dp"
 android:layout_marginRight="5dp"
 android:gravity="center"
 android:text="送玫瑰"
 android:textColor="#333"
 android:textSize="12sp" />
 <Button
 android:id="@+id/btn_gift03"
 android:layout_width="40dp"
 android:layout_height="70dp"
 android:layout_marginRight="5dp"
 android:gravity="center"
 android:text="送爱心"
 android:textColor="#333"
 android:textSize="12sp" />
 <Button
 android:id="@+id/btn_gift04"
 android:layout_width="40dp"
 android:layout_height="70dp"
 android:layout_marginRight="5dp"
 android:gravity="center"
 android:text="送蛋糕"
 android:textColor="#333"
 android:textSize="12sp" />
 </LinearLayout>
 <LinearLayout
 android:id="@+id/ll_inputparent"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:layout_marginTop="5dp"
 android:background="@android:color/white"
 android:paddingLeft="10dp"
 android:paddingRight="10dp"
 android:visibility="gone">
 <LinearLayout
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:gravity="center_vertical"
 android:orientation="horizontal">
 <EditText
 android:id="@+id/et_chat"
 android:layout_width="0dp"
 android:layout_height="wrap_content"
 android:layout_weight="1"
 android:background="@android:color/white"
 android:hint="在此输入你要说的话!"
 android:maxLength="30"
 android:paddingTop="10dp"
 android:paddingBottom="10dp"
 android:textColor="#888889"
 android:textColorHint="#c8c8c8"
 android:textSize="12sp" />
 <TextView
 android:id="@+id/tv_send"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:layout_marginLeft="10dp"
 android:background="@android:color/holo_blue_bright"
 android:paddingLeft="10dp"
 android:paddingTop="5dp"
 android:paddingRight="10dp"
 android:paddingBottom="5dp"
 android:text="发送"
 android:textColor="@android:color/white"
 android:textSize="12sp" />
 </LinearLayout>
 </LinearLayout>
 </FrameLayout>
</RelativeLayout>

EmptyFrag:

/**
 * 空的fragment
 */
public class EmptyFrag extends Fragment {
 @Nullable
 @Override
 public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
 return inflater.inflate(R.layout.frag_empty, null);
 }
}

frag_empty.xml:

<?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="match_parent"
 android:background="@android:color/transparent"
 android:orientation="vertical">
</LinearLayout>

在MainActivity中使用FrameLayout布局,将观众功能交互页面 InteractiveFrag 覆盖在 直播页面LiveFrag上面。

MainActivity:

public class MainActivity extends AppCompatActivity {

 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 // 加载直播fragment
 LiveFrag liveFrag = new LiveFrag();
 getSupportFragmentManager().beginTransaction().add(R.id.fl_root, liveFrag).commit();
 // 加载
 new InteractiveFrag().show(getSupportFragmentManager(), "InteractiveFrag");
 }
}

activity_main.xml :

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="match_parent"
 android:layout_height="match_parent">
 <FrameLayout
 android:id="@+id/fl_root"
 android:layout_width="match_parent"
 android:layout_height="match_parent" />
</RelativeLayout>

用户交互页实现

MagicTextView动画效果

MagicTextView代码在文章最后展示。

我们先实现如下动画效果

<com.hongx.zhibo.utils.MagicTextView
 android:id="@+id/mtv_giftNum"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:layout_centerVertical="true"
 android:layout_marginLeft="5dp"
 android:layout_toRightOf="@+id/rlparent"
 android:includeFontPadding="false"
 android:text="x1"
 android:textColor="@android:color/holo_red_dark"
 android:textSize="30sp"
 android:textStyle="bold"
 app:strokeColor="@android:color/white"
 app:strokeJoinStyle="miter"
 app:strokeWidth="2" />

动画:

 public class NumberAnim {
 private Animator lastAnimator;
 public void showAnimator(View v) {
 if (lastAnimator != null) {
 lastAnimator.removeAllListeners();
 lastAnimator.cancel();
 lastAnimator.end();
 }
 ObjectAnimator animScaleX = ObjectAnimator.ofFloat(v, "scaleX", 1.3f, 1.0f);
 ObjectAnimator animScaleY = ObjectAnimator.ofFloat(v, "scaleY", 1.3f, 1.0f);
 AnimatorSet animSet = new AnimatorSet();
 animSet.playTogether(animScaleX, animScaleY);
 animSet.setDuration(200);
 lastAnimator = animSet;
 animSet.start();
 }
 }

 mtv_giftNum.setText("x" + count);
 giftNumberAnim = new NumberAnim(); // 初始化数字动画
 mtv_giftNum.setOnClickListener(new View.OnClickListener() {
 @Override
 public void onClick(View v) {
 count++;
 mtv_giftNum.setText("x" + count);
 giftNumberAnim.showAnimator(mtv_giftNum);
 }
 });

礼物进入时动画

进入动画设置为decelerate_interpolator减速插值器:

<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
 android:duration="500"
 android:fromXDelta="-100%p"
 android:interpolator="@android:anim/decelerate_interpolator"
 android:toYDelta="0%p">
</translate>

 /**
 * 刷礼物的方法
 */
 private void showGift(String tag) {
 View newGiftView = ll_gift_group.findViewWithTag(tag);
 // 是否有该tag类型的礼物
 if (newGiftView == null) {
 // 获取礼物
 newGiftView = getNewGiftView(tag);
 ll_gift_group.addView(newGiftView);

 // 播放动画
 newGiftView.startAnimation(inAnim);
 final MagicTextView mtv_giftNum = newGiftView.findViewById(R.id.mtv_giftNum);
 inAnim.setAnimationListener(new Animation.AnimationListener() {

 @Override
 public void onAnimationStart(Animation animation) {
 }

 @Override
 public void onAnimationRepeat(Animation animation) {
 }

 @Override
 public void onAnimationEnd(Animation animation) {
 giftNumberAnim.showAnimator(mtv_giftNum);
 }
 });
 } else {
 // 如果列表中已经有了该类型的礼物,则不再新建,直接拿出
 // 更新标识,记录最新修改的时间,用于回收判断
 ImageView iv_gift = newGiftView.findViewById(R.id.iv_gift);
 iv_gift.setTag(System.currentTimeMillis());

 // 更新标识,更新记录礼物个数
 MagicTextView mtv_giftNum = newGiftView.findViewById(R.id.mtv_giftNum);
 int giftCount = (int) mtv_giftNum.getTag() + 1; // 递增
 mtv_giftNum.setText("x" + giftCount);
 mtv_giftNum.setTag(giftCount);
 giftNumberAnim.showAnimator(mtv_giftNum);
 }
 }

 /**
 * 获取礼物
 */
 private View getNewGiftView(String tag) {

 // 添加标识, 该view若在layout中存在,就不在生成(用于findViewWithTag判断是否存在)
 View giftView = LayoutInflater.from(myContext).inflate(R.layout.item_gift, null);
 giftView.setTag(tag);

 // 添加标识, 记录生成时间,回收时用于判断是否是最新的,回收最老的
 ImageView iv_gift = giftView.findViewById(R.id.iv_gift);
 iv_gift.setTag(System.currentTimeMillis());

 // 添加标识,记录礼物个数
 MagicTextView mtv_giftNum = giftView.findViewById(R.id.mtv_giftNum);
 mtv_giftNum.setTag(1);
 mtv_giftNum.setText("x1");

 switch (tag) {
 case "gift01":
 iv_gift.setImageResource(GiftIcon[0]);
 break;
 case "gift02":
 iv_gift.setImageResource(GiftIcon[1]);
 break;
 case "gift03":
 iv_gift.setImageResource(GiftIcon[2]);
 break;
 case "gift04":
 iv_gift.setImageResource(GiftIcon[3]);
 break;
 }

 LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
 lp.topMargin = 10;
 giftView.setLayoutParams(lp);

 return giftView;
 }

 @Override
 public void onClick(View v) {
 switch (v.getId()) {
 case R.id.btn_gift01: // 礼物1,送香皂
 showGift("gift01");
 break;
 case R.id.btn_gift02: // 礼物2,送玫瑰
 showGift("gift02");
 break;
 case R.id.btn_gift03: // 礼物3,送爱心
 showGift("gift03");
 break;
 case R.id.btn_gift04: // 礼物4,送蛋糕
 showGift("gift04");
 break;
 }
 }

礼物移出动画

实现的效果如下:

礼物移出时使用accelerate_interpolator加速差值器

<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
 android:duration="500"
 android:fromYDelta="0%p"
 android:interpolator="@android:anim/accelerate_interpolator"
 android:toYDelta="-100%p">
</translate>

 /**
 * 移除礼物列表里的giftView
 */
 private void removeGiftView(final int index) {
 // 移除列表,外加退出动画
 final View removeGiftView = ll_gift_group.getChildAt(index);
 outAnim.setAnimationListener(new Animation.AnimationListener() {

 @Override
 public void onAnimationStart(Animation animation) {
 }

 @Override
 public void onAnimationRepeat(Animation animation) {
 }

 @Override
 public void onAnimationEnd(Animation animation) {
 ll_gift_group.removeViewAt(index);
 }
 });

 // 开启动画,因为定时原因,所以可能是在子线程
 getActivity().runOnUiThread(new Runnable() {
 @Override
 public void run() {
 removeGiftView.startAnimation(outAnim);
 }
 });
 }

如果显示的礼物大于3种,就将最早的那种礼物移除:

// 是否有该tag类型的礼物
 if (newGiftView == null) {
 // 判断礼物列表是否已经有3个了,如果有那么删除掉一个没更新过的, 然后再添加新进来的礼物,始终保持只有3个
 if (ll_gift_group.getChildCount() >= 3) {
 // 获取前2个元素的最后更新时间
 View giftView01 = ll_gift_group.getChildAt(0);
 ImageView iv_gift01 = giftView01.findViewById(R.id.iv_gift);
 long lastTime1 = (long) iv_gift01.getTag();

 View giftView02 = ll_gift_group.getChildAt(1);
 ImageView iv_gift02 = giftView02.findViewById(R.id.iv_gift);
 long lastTime2 = (long) iv_gift02.getTag();

 if (lastTime1 > lastTime2) { // 如果第二个View显示的时间比较长
 removeGiftView(1);
 } else { // 如果第一个View显示的时间长
 removeGiftView(0);
 }
 }
...

开启定时清理礼物列表

礼物显示超过一定时间,自动将礼物在礼物列表中移除:

 /**
 * 定时清理礼物列表信息
 */
 private void clearTiming() {
 Timer timer = new Timer();
 timer.schedule(new TimerTask() {

 @Override
 public void run() {
 int childCount = ll_gift_group.getChildCount();
 long nowTime = System.currentTimeMillis();
 for (int i = 0; i < childCount; i++) {

 View childView = ll_gift_group.getChildAt(i);
 ImageView iv_gift = (ImageView) childView.findViewById(R.id.iv_gift);
 long lastUpdateTime = (long) iv_gift.getTag();

 // 更新超过3秒就刷新
 if (nowTime - lastUpdateTime >= 3000) {
 removeGiftView(i);
 }
 }
 }
 }, 0, 3000);
 }

聊天实现

case R.id.tv_chat:// 聊天
 tv_chat.setVisibility(View.GONE);
 ll_inputparent.setVisibility(View.VISIBLE);
 ll_inputparent.requestFocus(); // 获取焦点
 showKeyboard();
 break;
 case R.id.tv_send:// 发送消息
 String chatMsg = et_chat.getText().toString();
 if (!TextUtils.isEmpty(chatMsg)) {
 messageData.add("小明: " + chatMsg);
 et_chat.setText("");
 messageAdapter.NotifyAdapter(messageData);
 lv_message.setSelection(messageData.size());
 }
 hideKeyboard();
 break;

 /**
 * 显示软键盘
 */
 private void showKeyboard() {
 InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
 imm.showSoftInput(et_chat, InputMethodManager.SHOW_FORCED);
 }

 /**
 * 隐藏软键盘
 */
 public void hideKeyboard() {
 InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
 imm.hideSoftInputFromWindow(et_chat.getWindowToken(), 0);
 }

 view.setOnClickListener(new View.OnClickListener() {

 @Override
 public void onClick(View v) {
 if (ll_inputparent.getVisibility() == View.VISIBLE) {
 tv_chat.setVisibility(View.VISIBLE);
 ll_inputparent.setVisibility(View.GONE);
 hideKeyboard();
 }
 }
 });

 // 软键盘监听
 SoftKeyBoardListener.setListener(getActivity(), new SoftKeyBoardListener.OnSoftKeyBoardChangeListener() {
 @Override
 public void keyBoardShow(int height) {/*软键盘显示:执行隐藏title动画,并修改listview高度和装载礼物容器的高度*/

 // 输入文字时的界面退出动画
 AnimatorSet animatorSetHide = new AnimatorSet();
 ObjectAnimator leftOutAnim = ObjectAnimator.ofFloat(rl_num, "translationX", 0, -rl_num.getWidth());
 ObjectAnimator topOutAnim = ObjectAnimator.ofFloat(ll_anchor, "translationY", 0, -ll_anchor.getHeight());
 animatorSetHide.playTogether(leftOutAnim, topOutAnim);
 animatorSetHide.setDuration(300);
 animatorSetHide.start();
 // 改变listview的高度
 dynamicChangeListviewH(90);
 dynamicChangeGiftParentH(true);
 }

 @Override
 public void keyBoardHide(int height) {/*软键盘隐藏:隐藏聊天输入框并显示聊天按钮,执行显示title动画,并修改listview高度和装载礼物容器的高度*/
 tv_chat.setVisibility(View.VISIBLE);
 ll_inputparent.setVisibility(View.GONE);
 // 输入文字时的界面进入时的动画
 AnimatorSet animatorSetShow = new AnimatorSet();
 ObjectAnimator leftInAnim = ObjectAnimator.ofFloat(rl_num, "translationX", -rl_num.getWidth(), 0);
 ObjectAnimator topInAnim = ObjectAnimator.ofFloat(ll_anchor, "translationY", -ll_anchor.getHeight(), 0);
 animatorSetShow.playTogether(leftInAnim, topInAnim);
 animatorSetShow.setDuration(300);
 animatorSetShow.start();

 // 改变listview的高度
 dynamicChangeListviewH(150);
 dynamicChangeGiftParentH(false);
 }
 });

 /**
 * 动态的修改listview的高度
 */
 private void dynamicChangeListviewH(int heightPX) {
 ViewGroup.LayoutParams layoutParams = lv_message.getLayoutParams();
 layoutParams.height = DisplayUtil.dip2px(getActivity(), heightPX);
 lv_message.setLayoutParams(layoutParams);
 }

 /**
 * 动态修改礼物父布局的高度
 */
 private void dynamicChangeGiftParentH(boolean showhide) {
 if (showhide) {// 如果软键盘显示中
 if (ll_gift_group.getChildCount() != 0) {

 // 判断是否有礼物显示,如果有就修改父布局高度,如果没有就不作任何操作
 ViewGroup.LayoutParams layoutParams = ll_gift_group.getLayoutParams();
 layoutParams.height = ll_gift_group.getChildAt(0).getHeight();
 ll_gift_group.setLayoutParams(layoutParams);
 }
 } else {
 // 如果软键盘隐藏中
 // 就将装载礼物的容器的高度设置为包裹内容
 ViewGroup.LayoutParams layoutParams = ll_gift_group.getLayoutParams();
 layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
 ll_gift_group.setLayoutParams(layoutParams);
 }
 }

MagicTextView代码

/**
 * 该自定义view是用于显示礼物数字的,加了些效果,内发光,阴影等
 */
public class MagicTextView extends TextView {
 private ArrayList<Shadow> outerShadows;
 private ArrayList<Shadow> innerShadows;
 private WeakHashMap<String, Pair<Canvas, Bitmap>> canvasStore;
 private Canvas tempCanvas;
 private Bitmap tempBitmap;
 private Drawable foregroundDrawable;
 private float strokeWidth;
 private Integer strokeColor;
 private Join strokeJoin;
 private float strokeMiter;
 private int[] lockedCompoundPadding;
 private boolean frozen = false;
 public MagicTextView(Context context) {
 super(context);
 init(null);
 }
 public MagicTextView(Context context, AttributeSet attrs) {
 super(context, attrs);
 init(attrs);
 }
 public MagicTextView(Context context, AttributeSet attrs, int defStyle) {
 super(context, attrs, defStyle);
 init(attrs);
 }
 public void init(AttributeSet attrs) {
 outerShadows = new ArrayList<Shadow>();
 innerShadows = new ArrayList<Shadow>();
 if (canvasStore == null) {
 canvasStore = new WeakHashMap<String, Pair<Canvas, Bitmap>>();
 }
 if (attrs != null) {
 TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.MagicTextView);
 String typefaceName = a.getString(R.styleable.MagicTextView_typeface);
 if (typefaceName != null) {
 Typeface tf = Typeface.createFromAsset(getContext().getAssets(), String.format("fonts/%s.ttf", typefaceName));
 setTypeface(tf);
 }
 if (a.hasValue(R.styleable.MagicTextView_foreground)) {
 Drawable foreground = a.getDrawable(R.styleable.MagicTextView_foreground);
 if (foreground != null) {
 this.setForegroundDrawable(foreground);
 } else {
 this.setTextColor(a.getColor(R.styleable.MagicTextView_foreground, 0xff000000));
 }
 }
 if (a.hasValue(R.styleable.MagicTextView_innerShadowColor)) {
 this.addInnerShadow(a.getFloat(R.styleable.MagicTextView_innerShadowRadius, 0),
 a.getFloat(R.styleable.MagicTextView_innerShadowDx, 0),
 a.getFloat(R.styleable.MagicTextView_innerShadowDy, 0),
 a.getColor(R.styleable.MagicTextView_innerShadowColor, 0xff000000));
 }
 if (a.hasValue(R.styleable.MagicTextView_outerShadowColor)) {
 this.addOuterShadow(a.getFloat(R.styleable.MagicTextView_outerShadowRadius, 0),
 a.getFloat(R.styleable.MagicTextView_outerShadowDx, 0),
 a.getFloat(R.styleable.MagicTextView_outerShadowDy, 0),
 a.getColor(R.styleable.MagicTextView_outerShadowColor, 0xff000000));
 }
 if (a.hasValue(R.styleable.MagicTextView_strokeColor)) {
 float strokeWidth = a.getFloat(R.styleable.MagicTextView_strokeWidth, 1);
 int strokeColor = a.getColor(R.styleable.MagicTextView_strokeColor, 0xff000000);
 float strokeMiter = a.getFloat(R.styleable.MagicTextView_strokeMiter, 10);
 Join strokeJoin = null;
 switch (a.getInt(R.styleable.MagicTextView_strokeJoinStyle, 0)) {
 case (0):
 strokeJoin = Join.MITER;
 break;
 case (1):
 strokeJoin = Join.BEVEL;
 break;
 case (2):
 strokeJoin = Join.ROUND;
 break;
 }
 this.setStroke(strokeWidth, strokeColor, strokeJoin, strokeMiter);
 }
 }
 }
 public void setStroke(float width, int color, Join join, float miter) {
 strokeWidth = width;
 strokeColor = color;
 strokeJoin = join;
 strokeMiter = miter;
 }
 public void setStroke(float width, int color) {
 setStroke(width, color, Join.MITER, 10);
 }
 public void addOuterShadow(float r, float dx, float dy, int color) {
 if (r == 0) {
 r = 0.0001f;
 }
 outerShadows.add(new Shadow(r, dx, dy, color));
 }
 public void addInnerShadow(float r, float dx, float dy, int color) {
 if (r == 0) {
 r = 0.0001f;
 }
 innerShadows.add(new Shadow(r, dx, dy, color));
 }
 public void clearInnerShadows() {
 innerShadows.clear();
 }
 public void clearOuterShadows() {
 outerShadows.clear();
 }
 public void setForegroundDrawable(Drawable d) {
 this.foregroundDrawable = d;
 }
 public Drawable getForeground() {
 return this.foregroundDrawable == null ? this.foregroundDrawable : new ColorDrawable(this.getCurrentTextColor());
 }
 @Override
 public void onDraw(Canvas canvas) {
 super.onDraw(canvas);
 freeze();
 Drawable restoreBackground = this.getBackground();
 Drawable[] restoreDrawables = this.getCompoundDrawables();
 int restoreColor = this.getCurrentTextColor();
 this.setCompoundDrawables(null, null, null, null);
 for (Shadow shadow : outerShadows) {
 this.setShadowLayer(shadow.r, shadow.dx, shadow.dy, shadow.color);
 super.onDraw(canvas);
 }
 this.setShadowLayer(0, 0, 0, 0);
 this.setTextColor(restoreColor);
 if (this.foregroundDrawable != null && this.foregroundDrawable instanceof BitmapDrawable) {
 generateTempCanvas();
 super.onDraw(tempCanvas);
 Paint paint = ((BitmapDrawable) this.foregroundDrawable).getPaint();
 paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP));
 this.foregroundDrawable.setBounds(canvas.getClipBounds());
 this.foregroundDrawable.draw(tempCanvas);
 canvas.drawBitmap(tempBitmap, 0, 0, null);
 tempCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
 }
 if (strokeColor != null) {
 TextPaint paint = this.getPaint();
// paint.setTextAlign(Paint.Align.CENTER);
 paint.setStyle(Style.STROKE);
 paint.setStrokeJoin(strokeJoin);
 paint.setStrokeMiter(strokeMiter);
 this.setTextColor(strokeColor);
 paint.setStrokeWidth(strokeWidth);
 super.onDraw(canvas);
 paint.setStyle(Style.FILL);
 this.setTextColor(restoreColor);
 }
 if (innerShadows.size() > 0) {
 generateTempCanvas();
 TextPaint paint = this.getPaint();
 for (Shadow shadow : innerShadows) {
 this.setTextColor(shadow.color);
 super.onDraw(tempCanvas);
 this.setTextColor(0xFF000000);
 paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
 paint.setMaskFilter(new BlurMaskFilter(shadow.r, BlurMaskFilter.Blur.NORMAL));
 tempCanvas.save();
 tempCanvas.translate(shadow.dx, shadow.dy);
 super.onDraw(tempCanvas);
 tempCanvas.restore();
 canvas.drawBitmap(tempBitmap, 0, 0, null);
 tempCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
 paint.setXfermode(null);
 paint.setMaskFilter(null);
 this.setTextColor(restoreColor);
 this.setShadowLayer(0, 0, 0, 0);
 }
 }
 if (restoreDrawables != null) {
 this.setCompoundDrawablesWithIntrinsicBounds(restoreDrawables[0], restoreDrawables[1], restoreDrawables[2], restoreDrawables[3]);
 }
 this.setBackgroundDrawable(restoreBackground);
 this.setTextColor(restoreColor);
 unfreeze();
 }
 private void generateTempCanvas() {
 String key = String.format("%dx%d", getWidth(), getHeight());
 Pair<Canvas, Bitmap> stored = canvasStore.get(key);
 if (stored != null) {
 tempCanvas = stored.first;
 tempBitmap = stored.second;
 } else {
 tempCanvas = new Canvas();
 tempBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
 tempCanvas.setBitmap(tempBitmap);
 canvasStore.put(key, new Pair<Canvas, Bitmap>(tempCanvas, tempBitmap));
 }
 }
 public void freeze() {
 lockedCompoundPadding = new int[]{
 getCompoundPaddingLeft(),
 getCompoundPaddingRight(),
 getCompoundPaddingTop(),
 getCompoundPaddingBottom()
 };
 frozen = true;
 }
 public void unfreeze() {
 frozen = false;
 }
 @Override
 public void requestLayout() {
 if (!frozen) super.requestLayout();
 }
 @Override
 public void postInvalidate() {
 if (!frozen) super.postInvalidate();
 }
 @Override
 public void postInvalidate(int left, int top, int right, int bottom) {
 if (!frozen) super.postInvalidate(left, top, right, bottom);
 }
 @Override
 public void invalidate() {
 if (!frozen) super.invalidate();
 }
 @Override
 public void invalidate(Rect rect) {
 if (!frozen) super.invalidate(rect);
 }
 @Override
 public void invalidate(int l, int t, int r, int b) {
 if (!frozen) super.invalidate(l, t, r, b);
 }
 @Override
 public int getCompoundPaddingLeft() {
 return !frozen ? super.getCompoundPaddingLeft() : lockedCompoundPadding[0];
 }
 @Override
 public int getCompoundPaddingRight() {
 return !frozen ? super.getCompoundPaddingRight() : lockedCompoundPadding[1];
 }
 @Override
 public int getCompoundPaddingTop() {
 return !frozen ? super.getCompoundPaddingTop() : lockedCompoundPadding[2];
 }
 @Override
 public int getCompoundPaddingBottom() {
 return !frozen ? super.getCompoundPaddingBottom() : lockedCompoundPadding[3];
 }
 public static class Shadow {
 float r;
 float dx;
 float dy;
 int color;

 public Shadow(float r, float dx, float dy, int color) {
 this.r = r;
 this.dx = dx;
 this.dy = dy;
 this.color = color;
 }
 }
}

Github:https://github.com/345166018/AndroidUI/tree/master/HxZhibo

总结

以上所述是小编给大家介绍的Android仿直播类app赠送礼物功能,希望对大家有所帮助!

(0)

相关推荐

  • Android开发仿映客送礼物效果

    这里写链接内容仿映客送小礼物的特效,顺便复习一下属性动画,话不多说先看效果图. 需求分析 可以看到整个动画有几部分组成,那我们就把每个部分拆分出来各个击破. 1.要显示那些内容以及内容间的位置关系? 可以看到我们要显示用户头像,昵称,礼物图标以及数量.所以这里我选择用FrameLayout来作为根布局. 2.需要哪些动画以及动画的执行顺序? a.首先是整体从左到右飞入并有一个回弹(translationX + OvershootInterpolator) b.然后是礼物从左到右飞入而且是一个带减

  • Android直播app送礼物连击动画效果(实例代码)

    最近在做公司的直播项目,需要实现一个观看端连击送礼物的控件: 直接上代码: /** * @author yangyinglong on 2017/7/11 16:52. * @Description: todo(这里用一句话描述这个类的作用) * @Copyright Copyright (c) 2017 Tuandai Inc. All Rights Reserved. */ public class CustomGiftView extends LinearLayout { private

  • Android仿直播类app赠送礼物功能

    直播界面 实现的是播放本地的视频文件: /** * 直播界面,用于对接直播功能 */ public class LiveFrag extends Fragment { private ImageView img_thumb; private VideoView video_view; @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup containe

  • iOS直播类APP开发流程解析

    本文为大家分享了iOS直播类APP开发流程,供大家参考,具体内容如下 一 . 音视频处理的一般流程: 数据采集→数据编码→数据传输(流媒体服务器) →解码数据→播放显示 1.数据采集: 摄像机及拾音器收集视频及音频数据,此时得到的为原始数据 涉及技术或协议: 摄像机:CCD.CMOS 拾音器:声电转换装置(咪头).音频放大电路 2.数据编码: 使用相关硬件或软件对音视频原始数据进行编码处理(数字化)及加工(如音视频混合.打包封装等),得到可用的音视频数据 涉及技术或协议: 编码方式:CBR.VB

  • Android仿QQ空间动态界面分享功能

    先看看效果: 用极少的代码实现了 动态详情 及 二级评论 的 数据获取与处理 和 UI显示与交互,并且高解耦.高复用.高灵活. 动态列表界面MomentListFragment支持 下拉刷新与上拉加载 和 模糊搜索,反复快速滑动仍然非常流畅. 缓存机制使得数据可在启动界面后瞬间加载完成. 动态详情界面MomentActivity支持 (取消)点赞.(删除)评论.点击姓名跳到个人详情 等. 只有1张图片时图片放大显示,超过1张则按九宫格显示. 用到的CommentContainerView和Mom

  • Android 仿微信图像拍摄和选择界面功能(代码分享)

    插件运行后的画面如下: 下面这张图对图像进行筛选,根据照片产生的源头分(QQ和微信和相机) 点击某文件夹后,可以查看该文件夹下包含的所有的图片 图片选择界面 选中后就跳到已经选择界面的窗口,并且可以对该吃图片上传进行简要的描述 首先我想说明的是这个插件默认是不进行图片筛选的,打开app后会有几十个文件夹,但是个人认为开发中常用的图片基本都来自于QQ中拍摄的照片,微信中拍摄的照片,以及相机直接拍摄的照片,因此我对这个插件进行过滤以及文件夹名称的更改,具体做法,主要是对AlbumHelper类bui

  • Android 仿网易新闻客户端分类排序功能

    先来看看网易新闻客户端以及自己实现的效果图,效果当然还是网易的好 gridviewsort.gif 如何实现拖拽一个Item 用WindowManager添加一个ImageView,并且将这个ImageView的显示图片设置成被拖拽item的截图,截图可以通过View的getDrawingCache获得.拖拽的时候,隐藏原始的item.处理触摸事件的ActionMove,调整ImageView的位置,跟随手指移动.在ActionUp的时候removeView GridView @Override

  • Android仿UC浏览器左右上下滚动功能

    本文要解决在侧滑菜单右边加个文本框,并能实现文本的上下滑动和菜单的左右滚动.这里推荐可以好好看看android的触摸事件的分发机制,这里我就不详细讲了,我只讲讲这个应用.要实现的功能就像UC浏览器(或其它手机浏览器)的左右滚动,切换网页,上下滚动,拖动内容. 本文的效果: 一.功能要求与实现 1.功能要求: (1)手指一开始按着屏幕左右移动时,只能左右滚动菜单,如果这时手指一直按着,而且上下移动了,那么菜单显示部分保持不变,但文本框也不上下移动!                       (2

  • Android仿微信发送语音消息的功能及示例代码

    微信的发送语音是有一个向上取消的,我们使用onTouchListener来监听手势,然后做出相应的操作就行了. 直接上代码: //语音操作对象 private MediaPlayer mPlayer = null; private MediaRecorder mRecorder = null; //语音文件保存路径 private String FileName = null; FileName = Environment.getExternalStorageDirectory().getAbs

  • Android仿微信通讯录滑动快速定位功能

    先给大家展示下效果图: 实现代码如下: 下面简单说下实现原理. public class IndexBar extends LinearLayout implements View.OnTouchListener { private static final String[] INDEXES = new String[]{"#", "A", "B", "C", "D", "E", &qu

  • Android仿直播特效之点赞飘心效果

    本文实例为大家分享了Android实现点赞飘心效果的具体代码,供大家参考,具体内容如下 一.概述 老规矩先上图 好了,基本就是这个样子,录完的视频用格式工厂转换完就这个样子了,将就看吧 二.定义我们自己的Layout /** * @author 刘洋巴金 * @date 2017-4-27 * * 定义我们自己的布局 * */ public class LoveLayout extends RelativeLayout{ private Context context; private Layo

  • Android仿微信选择图片和拍照功能

    本文实例为大家分享了 Android微信选择图片的具体代码,和微信拍照功能,供大家参考,具体内容如下 1.Android6.0系统,对于权限的使用都是需要申请,选择图片和拍照需要申请Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE这两个权限. if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageM

随机推荐