Android_弹幕_效果_学习

/**
@作者 : 西野奈留
@博客:http://blog.csdn.net/narunishino
@声明:本文仅在【CSDN 博客】发表。
*/

附:

  1. 本文是仅仅是对原文的部分内容添加解释和小修改,要看原文请移步:http://blog.csdn.net/books1958/article/details/46375591
  2. 图片来自于原文。

Android_弹幕_效果_学习_第1张图片

【一共5个类:MainActivity.java; TanmuBean.java; ScreenUtils.java; AnimationHelper.java; DecelerateAccelerateInterporator.java.】

【运行逻辑:

  1. 点击按钮。
  2. 新开一个『工作线程』。
  3. 在『工作线程』里轮询看看『有多少条弹幕』。
  4. 每隔500毫秒,『有多少条弹幕』,就给handler发送『多少条信息』。
  5. handlerMessage接收到一条信息后,就显示一条动画(弹幕)。

1.MainActivity.java;

import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.support.v7.app.AppCompatActivity;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;

import java.lang.ref.WeakReference;
import java.util.HashSet;
import java.util.Set;

public class MainActivity extends AppCompatActivity {

    private MyHandler handler;
    /** * 弹幕内容 */
    private TanmuBean tanmuBean;
    /** * 放置弹幕内容的容器【containerVG】 */
    private RelativeLayout containerVG;

    //【containerVG】的高度
    private int validHeightSpace;

    private View startTanmuView;

    private FrameLayout frameLayout;

    //-----------------------分隔符----------------------------

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initFrameTest();
        init();

        /** * 点击按钮后就开始弹幕 */
        startTanmuView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                /** * 容器里面还有子view的话就return(do nothing) */
                if (containerVG.getChildCount() > 0) {
                    return;
                }
                /** * 清除【Set集合】里面的所有数据; */
                existMarginValues.clear();
                /** * 新开一个线程【工作线程】 */
                new Thread(new CreateTanmuThread()).start();
            }
        });
    }

    //-----------------------分隔符----------------------------

    /** * 这个方法是测试用的,没有任务意味,可以删除。 */
    private void initFrameTest() {
        TextView textViewFrame = new TextView(this);
        textViewFrame.setTextSize(30);
        textViewFrame.setText("我是一个test啦");
        textViewFrame.setTextColor(Color.parseColor("#000000"));

        frameLayout = (FrameLayout) findViewById(R.id.frame_container_test);

        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        textViewFrame.setLayoutParams(lp);

        Animation animation = new TranslateAnimation(1080, -1080, 0, 0);
        animation.setDuration(3000);
        animation.setRepeatCount(20);
        textViewFrame.startAnimation(animation);

        frameLayout.addView(textViewFrame);
    }

    //-----------------------分隔符----------------------------

    /** * 初始化 */
    private void init() {

        containerVG = (RelativeLayout) findViewById(R.id.tanmu_container);
        startTanmuView = findViewById(R.id.startTanmu);
        handler = new MyHandler(this);
        tanmuBean = new TanmuBean();
        /** * 弹幕的内容 */
        tanmuBean.setItems(new String[]{"I need your help.", "测试一下",
                "弹幕这东西真不好做啊", "总是出现各种问题~~",
                "我最长--------------------------" +
                        "-------------我最长",
                "也不知道都是为什么?麻烦!", "哪位大神可以帮帮我啊?", "I need your help.",
                "测试一下", "弹幕这东西真不好做啊", "总是出现各种问题~~", "也不知道都是为什么?麻烦!",
                "哪位大神可以帮帮我啊?", "I need your help.",
                "测试一下", "弹幕这东西真不好做啊", "总是出现各种问题~~",
                "也不知道都是为什么?麻烦!", "哪位大神可以帮帮我啊?", "I need your help.", "测试一下",
                "弹幕这东西真不好做啊", "总是出现各种问题~~",
                "也不知道都是为什么?麻烦!", "哪位大神可以帮帮我啊?", "I need your help.",
                "测试一下", "弹幕这东西真不好做啊", "总是出现各种问题~~", "也不知道都是为什么?麻烦!",
                "哪位大神可以帮帮我啊?", "I need your help.",
                "测试一下", "弹幕这东西真不好做啊", "总是出现各种问题~~",
                "也不知道都是为什么?麻烦!", "哪位大神可以帮帮我啊?", "I need your help."});
    }

    //-----------------------分隔符----------------------------

    private class CreateTanmuThread implements Runnable {
        @Override
        public void run() {
            /** * 通过【tanmuBean.getItems()】 * 获得弹幕的内容 */
            int N = tanmuBean.getItems().length;
            for (int i = 0; i < N; i++) {
                /** * public final Message obtainMessage (int what, int arg1, int arg2) * 【obtainMessage().sendToTarget()】等同于【sendMessage()】,除了性能上的不同 * 作用:有多少条【弹幕】就给【handler】发送多少条【消息】 */
                handler.obtainMessage(1, i, 0).sendToTarget();
                /** * 类似【Thread.sleep(500)】;但是该方法会忽略【InterruptedException】 * 作用:每0.5s自动添加一条弹幕 */
                SystemClock.sleep(500);
            }
        }
    }

    //-----------------------分隔符----------------------------

    private static class MyHandler extends Handler {
        private WeakReference<MainActivity> ref;

        MyHandler(MainActivity ac) {
            ref = new WeakReference<>(ac);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what == 1) {
                MainActivity ac = ref.get();
                if (ac != null && ac.tanmuBean != null) {
                    int index = msg.arg1;
                    /** * 要发送的弹幕的内容 */
                    String content = ac.tanmuBean.getItems()[index];
                    /** * Math.random()返回【0和1】之间的小数, * 计算结果返回【16和24】之间的大小。 */
                    float textSize = (float) (ac.tanmuBean.getMinTextSize() * (1 + Math.random() * ac.tanmuBean.getRange()));
                    /** * 返回【灰色】 */
                    int textColor = ac.tanmuBean.getColor();

                    ac.showTanmu(content, textSize, textColor);
                }
            }
        }
    }

    //-----------------------分隔符----------------------------

    private void showTanmu(String content, float textSize, int textColor) {

        final TextView textView = new TextView(this);
        textView.setTextSize(textSize);
        textView.setText(content);
        textView.setTextColor(textColor);

        /** * 【containerVG.getRight()】:【containerVG】的最右边到它的【父控件】的最左边的长度。 * 【containerVG.getPaddingLeft()】:【containerVG】这个控件有没有【padding】,没有的话就为0. * 结果:得到这个控件的宽度。 */
        int leftMargin = containerVG.getRight() - containerVG.getLeft() - containerVG.getPaddingLeft();

        //计算本条弹幕的topMargin(随机值,但是与屏幕中已有的不重复)
        /** * 【getRandomTopMargin()】returns 【marginValue】. * 【marginValue】为textView距离【containerVG】顶端的高度。 */
        int verticalMargin = getRandomTopMargin();
        /** * 在动画那里会用到【getTag】 */
        textView.setTag(verticalMargin);

        /** * LayoutParams(int w, int h) * 这个【RelativeLayout】就是【containerVG】,因为,请看【showTanmu方法】中最下面的代码, * 【containerVG.addView(textView)】:把【textView】add进了这个RelativeLayout中去了。 * 在【new RelativeLayout.LayoutParams()】设置的参数就是该控件(这里是textView)而非RelativeLayout的参数。 */
        RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams
                (RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
        /** * 【params.addRule(RelativeLayout.ALIGN_PARENT_TOP)】 * 这里指【textView】与父布局【relativeLayout】顶端对齐 */
        params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
        params.topMargin = verticalMargin;
        /** * 设置【textView】的参数 */
        textView.setLayoutParams(params);
        textView.setGravity(Gravity.CENTER_HORIZONTAL);

        /** * 【leftMargin】指的是从控件【containerVG】的最右边开始。 */
        Animation anim = AnimationHelper.createTranslateAnim(this, leftMargin, -ScreenUtils.getScreenW(this));

        /** * 【动画基础】可参考【http://www.imooc.com/video/7362】 */
        anim.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {

            }

            @Override
            public void onAnimationEnd(Animation animation) {
                //移除该组件
                containerVG.removeView(textView);
                //移除占位
                int verticalMargin = (int) textView.getTag();
                existMarginValues.remove(verticalMargin);
            }

            @Override
            public void onAnimationRepeat(Animation animation) {

            }
        });
        textView.startAnimation(anim);

        containerVG.addView(textView);
    }

    //-----------------------分隔符----------------------------

    //记录当前仍在显示状态的弹幕的位置(避免重复)
    private Set<Integer> existMarginValues = new HashSet<>();
    private int linesCount;

    private int getRandomTopMargin() {
        if (validHeightSpace == 0) {
            /** * 【containerVG.getBottom()】:【containerVG】的底部离它的父控件的顶部的距离。 * 结果:得到【containerVG】的高度。 */
            validHeightSpace = containerVG.getBottom() - containerVG.getTop()
                    - containerVG.getPaddingTop() - containerVG.getPaddingBottom();
        }

        if (linesCount == 0) {
            /** * 【tanmuBean.getMinTextSize() * (1 + tanmuBean.getRange())】=【16*1.5】=24 * 计算可用的行数【linesCount】 */
            linesCount = validHeightSpace / ScreenUtils.dp2px(this, tanmuBean.getMinTextSize() * (1 + tanmuBean.getRange()));
            if (linesCount == 0) {
                throw new RuntimeException("Not enough space to show text.");
            }
        }

        //检查重叠
        while (true) {
            /** * 假设【linesCount】是5行,则【randomIndex】是随机选到{0,1,2,3,4,5}中的其中一个(整数)。 * (int)使得小数变为整数。例:1.X都等于1;0.X都等于0。 */
            int randomIndex = (int) (Math.random() * linesCount);

            /** * 【总高度】除以【行数】=【每行的高度】。 * 【每行的高度】乘以【随机数】=【marginValue】 * 【marginValue】为textView距离【containerVG】顶端的距离。 */
            int marginValue = randomIndex * (validHeightSpace / linesCount);

            /** * boolean contains(Object o) 如果此 set 包含指定元素,则返回 true。 * 【Set集合】【existMarginValues】里面包含这个【marginValue长度】吗, * 如果不包含就可以把这个【长度】发给【TextView】 */
            if (!existMarginValues.contains(marginValue)) {
                existMarginValues.add(marginValue);
                return marginValue;
            }
        }
    }

    //-----------------------分隔符----------------------------
}

2.TanmuBean.java

import android.graphics.Color;

public class TanmuBean {

    private String[] items;
    private int color;
    private int minTextSize;
    private float range;

    public TanmuBean() {
        //init default value
        color = Color.parseColor("#444444");
        minTextSize = 16;
        range = 0.5f;
    }

    public String[] getItems() {
        return items;
    }

    public void setItems(String[] items) {
        this.items = items;
    }

    public int getColor() {
        return color;
    }

    public void setColor(int color) {
        this.color = color;
    }

    public int getMinTextSize() {
        return minTextSize;
    }

    /** * 这个【方法】没有用到,只是多出来的没有删掉而已 */
    public void setMinTextSize(int minTextSize) {
        this.minTextSize = minTextSize;
    }

    public float getRange() {
        return range;
    }

    /** * 这个【方法】没有用到,只是多出来的没有删掉而已 */
    public void setRange(float range) {
        this.range = range;
    }
}

3.ScreenUtils.java

import android.content.Context;
import android.util.DisplayMetrics;
import android.util.Log;

public class ScreenUtils {

    private static int screenW;
    private static int screenH;
    private static float screenDensity;

    public static int getScreenW(Context context) {
        if (screenW == 0) {
            initScreen(context);
        }
        /** * 【screenW】是屏幕【宽度】 */
        return screenW;
    }

    public static int getScreenH(Context context) {
        if (screenH == 0) {
            initScreen(context);
        }
        return screenH;
    }

    public static float getScreenDensity(Context context) {
        if (screenDensity == 0) {
            initScreen(context);
        }
        return screenDensity;
    }

    public static void initScreen(Context context) {
        DisplayMetrics metric = context.getResources().getDisplayMetrics();
        screenW = metric.widthPixels;
        screenH = metric.heightPixels;
        screenDensity = metric.density;
    }

    /** * 根据手机的屏幕密度从 dp 的单位 转成为 px(像素) */
    public static int dp2px(Context context, float dpValue) {
        return (int) (dpValue * getScreenDensity(context) + 0.5f);
    }

    /** * 根据手机的屏幕密度从 px(像素) 的单位 转成为 dp */
    public static int px2dp(Context context, float pxValue) {
        return (int) (pxValue / getScreenDensity(context) + 0.5f);
    }
}

4.AnimationHelper.java

import android.content.Context;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;

/** * 动画工具类 */
public class AnimationHelper {
    /** * 创建平移动画 */
    public static Animation createTranslateAnim(Context context, int fromX, int toX) {
        /** * 第一个参数fromXDelta为动画起始时 X坐标上的移动位置 * 第二个参数toXDelta为动画结束时 X坐标上的移动位置 * 第三个参数fromYDelta为动画起始时Y坐标上的移动位置 * 第四个参数toYDelta为动画结束时Y坐标上的移动位置 * TranslateAnimation(float fromXDelta, float toXDelta,float fromYDelta, float toYDelta) */
        TranslateAnimation tlAnim = new TranslateAnimation(fromX, toX, 0, 0);
        /** * 【setDuration()】动画运行持续的时间。 * 【setInterpolator()】控制运行速度。 * 【setFillAfter()】让View对象在动画执行完毕后保留在终止位置。 */
        tlAnim.setDuration(4000);
        tlAnim.setInterpolator(new DecelerateAccelerateInterpolator());
        tlAnim.setFillAfter(true);
        tlAnim.setFillEnabled(true);

        return tlAnim;
    }
}

5.DecelerateAccelerateInterporator.java

import android.view.animation.Interpolator;
/** * 【Interpolator】是一个速度控制器,控制速度变化。 */
public class DecelerateAccelerateInterpolator implements Interpolator {
    @Override
    public float getInterpolation(float input) {
        /** * 把【input】return回去的话弹幕就是【匀速】从右到左。 */
        return input;
    }
}

参考:
『参考了很多文章,不一一写上,有需要的请联系补充。』

-2015/12/04-
-End-

你可能感兴趣的:(android,安卓,实现,效果,弹幕)