欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android跑马灯MarqueeView源码解析

程序员文章站 2024-02-16 19:29:04
跑马灯效果,大家可以去原作者浏览https://github.com/sfsheng0322/marqueeview 下面看自定义控件的代码 public...

跑马灯效果,大家可以去原作者浏览https://github.com/sfsheng0322/marqueeview
下面看自定义控件的代码

public class marqueeview extends viewflipper {

  private context mcontext;
  private list<string> notices;
  private boolean issetanimduration = false;
  private onitemclicklistener onitemclicklistener;

  private int interval = 2000;
  private int animduration = 500;
  private int textsize = 14;
  private int textcolor = 0xffffffff;

  private int gravity = gravity.left | gravity.center_vertical;
  private static final int text_gravity_left = 0, text_gravity_center = 1, text_gravity_right = 2;

  public marqueeview(context context, attributeset attrs) {
    super(context, attrs);
    init(context, attrs, 0);
  }

  private void init(context context, attributeset attrs, int defstyleattr) {
    this.mcontext = context;
    if (notices == null) {
      notices = new arraylist<>();
    }

    typedarray typedarray = getcontext().obtainstyledattributes(attrs, r.styleable.marqueeviewstyle, defstyleattr, 0);
    interval = typedarray.getinteger(r.styleable.marqueeviewstyle_mvinterval, interval);
    issetanimduration = typedarray.hasvalue(r.styleable.marqueeviewstyle_mvanimduration);
    animduration = typedarray.getinteger(r.styleable.marqueeviewstyle_mvanimduration, animduration);
    if (typedarray.hasvalue(r.styleable.marqueeviewstyle_mvtextsize)) {
      textsize = (int) typedarray.getdimension(r.styleable.marqueeviewstyle_mvtextsize, textsize);
      textsize = displayutil.px2sp(mcontext, textsize);
    }
    textcolor = typedarray.getcolor(r.styleable.marqueeviewstyle_mvtextcolor, textcolor);
    int gravitytype = typedarray.getint(r.styleable.marqueeviewstyle_mvgravity, text_gravity_left);
    switch (gravitytype) {
      case text_gravity_center:
        gravity = gravity.center;
        break;
      case text_gravity_right:
        gravity = gravity.right | gravity.center_vertical;
        break;
    }
    typedarray.recycle();

    setflipinterval(interval);

    animation animin = animationutils.loadanimation(mcontext, r.anim.anim_marquee_in);
    if (issetanimduration) animin.setduration(animduration);
    setinanimation(animin);

    animation animout = animationutils.loadanimation(mcontext, r.anim.anim_marquee_out);
    if (issetanimduration) animout.setduration(animduration);
    setoutanimation(animout);
  }

  // 根据公告字符串启动轮播
  public void startwithtext(final string notice) {
    if (textutils.isempty(notice)) return;
    getviewtreeobserver().addongloballayoutlistener(new viewtreeobserver.ongloballayoutlistener() {
      @override
      public void ongloballayout() {
        getviewtreeobserver().removeglobalonlayoutlistener(this);
        startwithfixedwidth(notice, getwidth());
      }
    });
  }

  // 根据公告字符串列表启动轮播
  public void startwithlist(list<string> notices) {
    setnotices(notices);
    start();
  }

  // 根据宽度和公告字符串启动轮播
  private void startwithfixedwidth(string notice, int width) {
    int noticelength = notice.length();
    int dpw = displayutil.px2dip(mcontext, width);
    int limit = dpw / textsize;
    if (dpw == 0) {
      throw new runtimeexception("please set marqueeview width !");
    }

    if (noticelength <= limit) {
      notices.add(notice);
    } else {
      int size = noticelength / limit + (noticelength % limit != 0 ? 1 : 0);
      for (int i = 0; i < size; i++) {
        int startindex = i * limit;
        int endindex = ((i + 1) * limit >= noticelength ? noticelength : (i + 1) * limit);
        notices.add(notice.substring(startindex, endindex));
      }
    }
    start();
  }

  // 启动轮播
  public boolean start() {
    if (notices == null || notices.size() == 0) return false;
    removeallviews();

    for (int i = 0; i < notices.size(); i++) {
      final textview textview = createtextview(notices.get(i), i);
      final int finali = i;
      textview.setonclicklistener(new onclicklistener() {
        @override
        public void onclick(view v) {
          if (onitemclicklistener != null) {
            onitemclicklistener.onitemclick(finali, textview);
          }
        }
      });
      addview(textview);
    }

    if (notices.size() > 1) {
      startflipping();
    }
    return true;
  }

  // 创建viewflipper下的textview
  private textview createtextview(string text, int position) {
    textview tv = new textview(mcontext);
    tv.setgravity(gravity);
    tv.settext(text);
    tv.settextcolor(textcolor);
    tv.settextsize(textsize);
    tv.settag(position);
    return tv;
  }

  public int getposition() {
    return (int) getcurrentview().gettag();
  }

  public list<string> getnotices() {
    return notices;
  }

  public void setnotices(list<string> notices) {
    this.notices = notices;
  }

  public void setonitemclicklistener(onitemclicklistener onitemclicklistener) {
    this.onitemclicklistener = onitemclicklistener;
  }

  public interface onitemclicklistener {
    void onitemclick(int position, textview textview);
  }

}

跑马灯view是继承viewflipper,可以看到他的结构体

Android跑马灯MarqueeView源码解析

其实viewflipper工作机制很简单,如上图,就是将添加到viewflipper中的子view按照顺序定时的显示是其中一个子view,其他的子view设置为gone状态

Android跑马灯MarqueeView源码解析

private context mcontext;
  private list<string> notices;
  private boolean issetanimduration = false;
  private onitemclicklistener onitemclicklistener;

  private int interval = 2000;
  private int animduration = 500;
  private int textsize = 14;
  private int textcolor = 0xffffffff;

  private int gravity = gravity.left | gravity.center_vertical;
  private static final int text_gravity_left = 0, text_gravity_center = 1, text_gravity_right = 2;

看出view的一些属性,上下文,集合,是否动画延迟,点击事件,跑马灯的时间间隔,动画延迟时间,字体大小颜色,设置位置在靠左垂直中间对齐,文字的位置常量。

修改marqueeview的构造方法,我们可以在右键generate快速生成。

<declare-styleable name="marqueeviewstyle">
    <attr name="mvinterval" format="integer|reference"/>
    <attr name="mvanimduration" format="integer|reference"/>
    <attr name="mvtextsize" format="dimension|reference"/>
    <attr name="mvtextcolor" format="color|reference"/>
    <attr name="mvgravity">
      <enum name="left" value="0"/>
      <enum name="center" value="1"/>
      <enum name="right" value="2"/>
    </attr>
  </declare-styleable>

typedarray typedarray = getcontext().obtainstyledattributes(attrs, r.styleable.marqueeviewstyle, defstyleattr, 0);

首先获取属性集合,获取一个mv的间隔,默认值2000

 issetanimduration = typedarray.hasvalue(r.styleable.marqueeviewstyle_mvanimduration);

是否设置动画时间的延迟

if (typedarray.hasvalue(r.styleable.marqueeviewstyle_mvtextsize)) {
      textsize = (int) typedarray.getdimension(r.styleable.marqueeviewstyle_mvtextsize, textsize);
      textsize = displayutil.px2sp(mcontext, textsize);
    }

假如设置有自定义文字大小,就获取然后px转成sp
获取控件位置,*设置
在后面要回收

typedarray.recycle();

setflipinterval(interval);设置滚屏间隔,单位毫秒

animation animin = animationutils.loadanimation(mcontext, r.anim.anim_marquee_in);
    if (issetanimduration) animin.setduration(animduration);
    setinanimation(animin);

    animation animout = animationutils.loadanimation(mcontext, r.anim.anim_marquee_out);
    if (issetanimduration) animout.setduration(animduration);
    setoutanimation(animout);

一进一出的动画效果

// 根据公告字符串启动轮播
public void startwithtext(final string notice)
暴露个公共方法,里面有测量view的getviewtreeobserver方法,里面内部类调用了startwithfixedwidth(notice, getwidth());方法

 // 根据宽度和公告字符串启动轮播
  private void startwithfixedwidth(string notice, int width) {
    int noticelength = notice.length();
    int dpw = displayutil.px2dip(mcontext, width);
    int limit = dpw / textsize;
    if (dpw == 0) {
      throw new runtimeexception("please set marqueeview width !");
    }

    if (noticelength <= limit) {
      notices.add(notice);
    } else {
      int size = noticelength / limit + (noticelength % limit != 0 ? 1 : 0);
      for (int i = 0; i < size; i++) {
        int startindex = i * limit;
        int endindex = ((i + 1) * limit >= noticelength ? noticelength : (i + 1) * limit);
        notices.add(notice.substring(startindex, endindex));
      }
    }
    start();
  }

转换得到一个dp的宽度,限制字数长度大小。假如字符串小于直接add。假如过长取余得到行数。然后循环,获取那行的头尾,末尾假如2行字数还是比总体长度大就取总体长度,假如小于总体长度,就取那行的末尾下表。
然后开始播放

 // 启动轮播
  public boolean start() {
    if (notices == null || notices.size() == 0) return false;
    removeallviews();

    for (int i = 0; i < notices.size(); i++) {
      final textview textview = createtextview(notices.get(i), i);
      final int finali = i;
      textview.setonclicklistener(new onclicklistener() {
        @override
        public void onclick(view v) {
          if (onitemclicklistener != null) {
            onitemclicklistener.onitemclick(finali, textview);
          }
        }
      });
      addview(textview);
    }

    if (notices.size() > 1) {
      startflipping();
    }
    return true;
  }

创建部署每行的tv

 // 创建viewflipper下的textview
  private textview createtextview(string text, int position) {
    textview tv = new textview(mcontext);
    tv.setgravity(gravity);
    tv.settext(text);
    tv.settextcolor(textcolor);
    tv.settextsize(textsize);
    tv.settag(position);
    return tv;
  }

然后将所有的textview add起来,然后开始播放。后面就是做个点击回调,然后set get这个公告的集合。
最后不要忘了在布局顶层加入

xmlns:app="http://schemas.android.com/apk/res-auto"

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。