改写控件之《自定义View,让你整个Layout像横向温度计一般》

转载请注明出处:王亟亟的大牛之路

恐惧自我受苦的人,已经正因自我的恐惧在受苦。

我们平时的进度条一般是下面这样子的

今天上的一个效果是这样的(初始化)

动起来后是这样的

给与用户一种新的体验吧,贴下项目结构

自定义控件:ProgressLayout

配套的监听事件:ProgressLayoutListener

适配器:RecylerListAdapter

对象类:Track(实际生产应该是 Json对象之类的)

OK,废话不多说,上代码

public class Track {

  private int trackId;
  private String songName;
  private String singerName;
  private int durationInSec;
  private boolean isPlaying = false;

  public Track(int trackId, String songName, String singerName, int durationInSec) {
    this.trackId = trackId;
    this.songName = songName;
    this.singerName = singerName;
    this.durationInSec = durationInSec;
  }

  public String getSongName() {
    return songName;
  }

  public void setSongName(String songName) {
    this.songName = songName;
  }

  public String getSingerName() {
    return singerName;
  }

  public void setSingerName(String singerName) {
    this.singerName = singerName;
  }

  public int getDurationInSec() {
    return durationInSec;
  }

  public void setDurationInSec(int durationInSec) {
    this.durationInSec = durationInSec;
  }

  public int getTrackId() {
    return trackId;
  }

  public void setTrackId(int trackId) {
    this.trackId = trackId;
  }

  public boolean isPlaying() {
    return isPlaying;
  }

  public void setIsPlaying(boolean isPlaying) {
    this.isPlaying = isPlaying;
  }
}

分析:各字段的get,set方法,用于给对象赋值。

RecylerListAdapter

public class RecylerListAdapter extends RecyclerView.Adapter<RecylerListAdapter.ViewHolder> {

  /**
   * 数据源集合
   */
  private List<Track> trackList;

  /**
   * 当前播放的对象。
   */
  private Track currentTrack;

  /**
   * 持续时间
   */
  private int currentDuration = 0;

  /**
   * 是否正在播放
   */
  private boolean isPlaying = false;

  private static final int SECOND_MS = 1000;

  /**
   * recyclerview中调用的Handle
   */
  private Handler mHandler = new Handler();

  /**
   * 计算秒数
   *
   */
  private final Runnable mRunnable = new Runnable() {
    @Override public void run() {
      currentDuration += 1;
      mHandler.postDelayed(mRunnable, SECOND_MS);
    }
  };

  /**
   * 传参
   */
  public void setTrackList(List<Track> trackList) {
    this.trackList = trackList;
    notifyDataSetChanged();
  }

  /**
   * 创建holder
   */
  @Override public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
    View view =
        LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.list_item, viewGroup, false);
    ViewHolder viewHolder = new ViewHolder(view);
    return viewHolder;
  }

  /**
   * 绑定对象
   */
  @Override public void onBindViewHolder(final ViewHolder viewHolder, final int i) {

    final Track track = trackList.get(i);

    viewHolder.textViewDuration.setText(calculateSongDuration(track.getDurationInSec()));
    viewHolder.textViewSong.setText(track.getSongName());
    viewHolder.textViewSinger.setText(track.getSingerName());
    viewHolder.imageViewAction.setBackgroundResource(R.drawable.play);
    viewHolder.progressLayout.setMaxProgress(track.getDurationInSec());

    if (currentTrack != null && currentTrack == track) {
      viewHolder.imageViewAction.setBackgroundResource(
          isPlaying ? R.drawable.pause : R.drawable.play);
      viewHolder.progressLayout.setCurrentProgress(currentDuration);
      if (isPlaying) viewHolder.progressLayout.start();
    } else {
      viewHolder.progressLayout.cancel();
    }

    viewHolder.imageViewAction.setOnClickListener(new View.OnClickListener() {
      @Override public void onClick(View v) {

        if (track != currentTrack) {
          currentTrack = track;
          mHandler.removeCallbacks(mRunnable);
          currentDuration = 0;
        }

        if (!viewHolder.progressLayout.isPlaying()) {
          isPlaying = true;
          viewHolder.progressLayout.start();
          mHandler.postDelayed(mRunnable, 0);
          viewHolder.imageViewAction.setBackgroundResource(R.drawable.pause);
          notifyDataSetChanged();
        } else {
          isPlaying = false;
          viewHolder.progressLayout.stop();
          mHandler.removeCallbacks(mRunnable);
          viewHolder.imageViewAction.setBackgroundResource(R.drawable.play);
          notifyDataSetChanged();
        }
      }
    });
    /*
    * 播放按钮的监听事件
    * */
    viewHolder.progressLayout.setProgressLayoutListener(new ProgressLayoutListener() {
      @Override public void onProgressCompleted() {
        viewHolder.imageViewAction.setBackgroundResource(R.drawable.play);
      }

      @Override public void onProgressChanged(int seconds) {
        viewHolder.textViewDuration.setText(calculateSongDuration(seconds));
      }
    });
  }

  /**
   * List大小
   */
  @Override public int getItemCount() {
    return trackList.size();
  }

  /**
   * 换成分钟
   */
  private String calculateSongDuration(int seconds) {
    return new StringBuilder(String.valueOf(seconds / 60))
        .append(":")
        .append(String.valueOf(seconds % 60))
        .toString();
  }

  /**
   * ViewHolder对象
   */
  public static class ViewHolder extends RecyclerView.ViewHolder {

    @Bind(R.id.imageviewAction) ImageView imageViewAction;
    @Bind(R.id.progressLayout) ProgressLayout progressLayout;
    @Bind(R.id.textviewSong) TextView textViewSong;
    @Bind(R.id.textviewSinger) TextView textViewSinger;
    @Bind(R.id.textviewDuration) TextView textViewDuration;

    public ViewHolder(View itemView) {
      super(itemView);
      ButterKnife.bind(this, itemView);
    }

  }
}

ProgressLayoutListener接口

public interface ProgressLayoutListener {
  void onProgressCompleted();
  void onProgressChanged(int seconds);
}

ProgressLayout

public class ProgressLayout extends View implements Animatable {

  private static final int COLOR_EMPTY_DEFAULT = 0x00000000;
  private static final int COLOR_LOADED_DEFAULT = 0x11FFFFFF;
  private static final int PROGRESS_SECOND_MS = 1000;

  private static Paint paintProgressLoaded;
  private static Paint paintProgressEmpty;

  private boolean isPlaying = false;
  private boolean isAutoProgress;

  private int mHeight;
  private int mWidth;
  private int maxProgress;
  private int currentProgress = 0;

  private Handler handlerProgress;

  private ProgressLayoutListener progressLayoutListener;

  public ProgressLayout(Context context) {
    this(context, null);
  }

  public ProgressLayout(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
  }

  public ProgressLayout(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init(context, attrs);
  }

  @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  public ProgressLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
    init(context, attrs);
  }

  @Override public boolean isRunning() {
    return isPlaying;
  }

  @Override public void start() {
    if (isAutoProgress) {
      isPlaying = true;
      handlerProgress.removeCallbacksAndMessages(null);
      handlerProgress.postDelayed(mRunnableProgress, 0);
    }
  }

  @Override public void stop() {
    isPlaying = false;
    handlerProgress.removeCallbacks(mRunnableProgress);
    postInvalidate();
  }

  @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    mWidth = MeasureSpec.getSize(widthMeasureSpec);
    mHeight = MeasureSpec.getSize(heightMeasureSpec);
  }

  @Override protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawRect(0, 0, mWidth, mHeight, paintProgressEmpty);
    canvas.drawRect(0, 0, calculatePositionIndex(currentProgress), mHeight, paintProgressLoaded);
  }

  private void init(Context context, AttributeSet attrs) {
    setWillNotDraw(false);
    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.progressLayout);
    isAutoProgress = a.getBoolean(R.styleable.progressLayout_autoProgress, true);
    maxProgress = a.getInt(R.styleable.progressLayout_maxProgress, 0);
    maxProgress = maxProgress * 10;
    int loadedColor = a.getColor(R.styleable.progressLayout_loadedColor, COLOR_LOADED_DEFAULT);
    int emptyColor = a.getColor(R.styleable.progressLayout_emptyColor, COLOR_EMPTY_DEFAULT);
    a.recycle();

    paintProgressEmpty = new Paint();
    paintProgressEmpty.setColor(emptyColor);
    paintProgressEmpty.setStyle(Paint.Style.FILL);
    paintProgressEmpty.setAntiAlias(true);

    paintProgressLoaded = new Paint();
    paintProgressLoaded.setColor(loadedColor);
    paintProgressLoaded.setStyle(Paint.Style.FILL);
    paintProgressLoaded.setAntiAlias(true);

    handlerProgress = new Handler();
  }

  private int calculatePositionIndex(int currentProgress) {
    return (currentProgress * mWidth) / maxProgress;
  }

  public boolean isPlaying() {
    return isPlaying;
  }

  public void cancel() {
    isPlaying = false;
    currentProgress = 0;
    handlerProgress.removeCallbacks(mRunnableProgress);
    postInvalidate();
  }

  public void setCurrentProgress(int currentProgress) {
    this.currentProgress = currentProgress * 10;
    postInvalidate();
  }

  public void setMaxProgress(int maxProgress) {
    this.maxProgress = maxProgress * 10;
    postInvalidate();
  }

  public void setAutoProgress(boolean isAutoProgress) {
    this.isAutoProgress = isAutoProgress;
  }

  public void setProgressLayoutListener(ProgressLayoutListener progressLayoutListener) {
    this.progressLayoutListener = progressLayoutListener;
  }

  private final Runnable mRunnableProgress = new Runnable() {
    @Override public void run() {
      if (isPlaying) {
        if (currentProgress == maxProgress) {
          if (progressLayoutListener != null) {
            progressLayoutListener.onProgressCompleted();
          }
          currentProgress = 0;
          setCurrentProgress(currentProgress);
          stop();
        } else {
          postInvalidate();
          currentProgress += 1;
          if (progressLayoutListener != null) {
            progressLayoutListener.onProgressChanged(currentProgress / 10);
          }
          handlerProgress.postDelayed(mRunnableProgress, PROGRESS_SECOND_MS / 10);
        }
      }
    }
  };
}

分析:

postInvalidate是刷新界面,使用postInvalidate则比较简单,不需要handler,直接在线程中调用postInvalidate即可。

invalidate是刷新页面,实例化一个Handler对象,并重写handleMessage方法调用invalidate()实现界面刷新;而在线程中通过sendMessage发送界面更新消息。

在init()方法内对我们的布局进行初始化,并new 一个Handle实例对之后的UI变化进行操作

具体内容可以看源码:

源码地址:http://yunpan.cn/cHHj3TwvaNhc5 访问密码 67e6

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-11 02:37:07

改写控件之《自定义View,让你整个Layout像横向温度计一般》的相关文章

Android学习笔记(九)——布局和控件的自定义

//此系列博文是<第一行Android代码>的学习笔记,如有错漏,欢迎指正! View是 Android中一种最基本的 UI组件,它可以在屏幕上绘制一块矩形区域,并能响应这块区域的各种事件,因此,我们使用的各种控件其实就是在 View的基础之上又添加了各自特有的功能.而ViewGroup 则是一种特殊的 View,它可以包含很多的子 View和子 ViewGroup,是一个用于放置控件和布局的容器.系统默认的所有控件都是直接或间接继承自 View 的,所用的所有布局都是直接或间接继承自 Vie

UI基本控件和自定义视图

UILabel 常用属性: UITextField 常用属性: 输入控制属性: 外观控制属性: 输入框让键盘回收的方法: 1.通过协议 (1)让AppDelegate成为输入框的代理对象 ,让TA去执行事件(AppDelegate接受输入框的协议) (2)接受了协议就要执行键盘回收的方法 (3)建立关系: 如:textField.delegate = self;(self指AppDelegate) 2.通过Tag值 (1)创建一个按钮添加触发事件,让Tag值的键盘回收 (2)按钮触发的事件方法如

使用谷歌提供的SwipeRefreshLayout下拉控件,并自定义实现下拉加载的功能

package com.loaderman.swiperefreshdemo; import android.os.Bundle; import android.os.Handler; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.app.AppCompatActivity; import android.view.Gravity; import android.view.View;

[转]一步一步玩控件:自定义TabControl——从山寨Safari开始

作者:野比 ([email protected]) 时间:May, 2012 封面图片为野比原创,请勿未经允许私自引用 #1-1 嗯,各位,又是我,生物钟颠倒的家伙. 今天我要山寨的是大名鼎鼎的Apple,传说中的「被山寨之王」. 没错,都被我山寨好几次了. 说起Apple,相信大家对他家的各种产品,不管他软还是硬,都有相当的好感. 最近Apple把自家的Web浏览器Safari升级到了第5版,并同步推出了Windows版,支持WinXP开始的全部Windows版本. 不得不说,这是一个很给力的

【转】C# 控件的自定义拖动、改变大小方法

在用VS的窗体设计器时,我们可以发现控件都是可以拖动的,并且还可以调整大小.怎么在自己的程序中可以使用上述功能呢? 下面的方法值得借鉴! using System; using System.Windows.Forms; using System.Drawing; namespace ControlSizeChangeEx { /// <summary> /// This class implements sizing and moving functions for /// runtime

WPF自定义控件与样式(10)-进度控件ProcessBar自定义样

一.前言 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等,若有不明白的地方可以参考本系列前面的文章,文末附有部分文章链接. 本文主要内容: ProcessBar自定义标准样式: ProcessBar自定义环形进度样式: 二.ProcessBar标准样式 效果图: ProcessBar的样式非常简单: <!--ProgressBar Style--> <Style TargetType="ProgressBar" x

改写控件之《仿iOS滑动枷锁样式的登录》

转载请注明出处:王亟亟的大牛之路 iOS的滑动解锁你的心系列(张翰梗)一直是个人觉得蛮好的看的一个东西,然后今天就把这样的一个控件应用到我们今天的Demo中让他滑动解锁. 样式是这样的:出处 初始化 滑好后 包目录: 流程–用户打开App–输入账号密码–滑动登录–正确/错误–登陆成功提示/弹出对话框. 很普遍的登陆流程,只是在具体实现上做一些改变,摆脱单一的登录,注册按钮等. MainActivity: public class MainActivity extends AppCompatAct

UIButton图片文字控件位置自定义(图片居右文字居左、图片居中文字居中、图片居左文字消失等)

在开发中经常会碰到需要对按钮中的图片文字位置做调整的需求.第一种方式是通过设置按钮中图片文字的偏移量.通过方法setTitleEdgeInsets和setImageEdgeInsets实现 代码如下: /*!**方式一***/ - (void)updateBtnStyle_rightImage:(UIButton *)btn { CGFloat btnImageWidth = btn.imageView.bounds.size.width; CGFloat btnLabelWidth = btn

vue表单控件绑定+自定义组件

vue表单控件绑定 v-model 在表单控件元素上创建双向数据绑定 文本框双向绑定 多选框演示 下拉列表演示 vue自定义组件 组件放在components目录下 组件基本要素:props  $emit 通过import导入自定义组件 制作一个倒计时组件: 1.在conponents目录下,新建一个time.vue 方法写在mouted声明周期函数内,代码如下: 然后在index.vue中使用组件: 我之前组件命名为time,可能与默认什么冲突了,然后报错不让用,所以改名成cyytime 但是

C# 控件的自定义拖动、改变大小方法

在用VS的窗体设计器时,我们可以发现控件都是可以拖动的,并且还可以调整大小.怎么在自己的程序中可以使用上述功能呢? 下面的方法值得借鉴! using System; using System.Windows.Forms; using System.Drawing; namespace ControlSizeChangeEx { /// <summary> /// This class implements sizing and moving functions for /// runtime