Thierryxc 的博客

每天进步一点点


  • 首页

  • 归档

  • 标签

GalleryByReact

发表于 2017-12-17

Gallery By React

根据慕课网教程写的React练手小项目:

  • index.js

    import React from 'react';
    import ReactDOM from 'react-dom';
    import './index.css';
    import Data from './data/imgData.json'
    
    //创建图片组件
     class ImgFigure  extends React.Component {
         constructor(props) {
             super(props);
             this.handleClick = this.handleClick.bind(this);//如果在调用handleClick方法后面加(),应该为其绑定this
         }
    
         handleClick(e) {//如果图片居中了就翻转,未居中则居中
             if (this.props.arrange.isCenter) {
                 this.props.inverse();
             } else {
                 this.props.center();
             }
             e.stopPropagation();//终止事件在传播过程的捕获、目标处理或起泡阶段进一步传播
             e.preventDefault();//阻止点击事件的默认行为
         }
    
         render() {
             var styleObj = {};
            //如果props属性中指定了这张图片的位置,则使用
            if (this.props.arrange.pos) {
                 styleObj.left = this.props.arrange.pos.left;
                 styleObj.top = this.props.arrange.pos.top;
               }
    
            //如果图片的旋转角度有值并且不为0,添加旋转角度
            if (this.props.arrange.rotate) {
                  (['Moz', 'Ms', 'Webkit', '']).forEach((value) => {
                    styleObj[value + 'Transform'] = 'rotate(' + this.props.arrange.rotate + 'deg)';
                  });
            }
            //如果图片居中,则设置zIndex使其在最上层
            if (this.props.arrange.isCenter) {
                styleObj.zIndex = 11;
            }
            //如果图片翻转,则添加“is-inverse”类
            let imgFigureClassName = 'img-figure';
            imgFigureClassName += this.props.arrange.isInverse ? ' is-inverse ' : '';
    
             return (
                 <figure style={styleObj} className={imgFigureClassName} onClick={this.handleClick}>
                     <img src={this.props.data.imgUrl}
                      alt={this.props.data.title}/>
                     <figcaption>
                     <h2 className='img-title'>{this.props.data.title}</h2>
                     </figcaption>
                     <div className="img-back" onClick={this.handleClick}>
                         <p>
                             {this.props.data.desc}
                         </p>
                     </div>
                 </figure>
             );
         }
    
     }
    
     //控制结构组件(思路基本与imgFigure相同)
     class ControllerUnit extends React.Component {   
        constructor(props) {
            super(props);
            this.handleClick = this.handleClick.bind(this);
        }
    
        handleClick(e) {
             if (this.props.arrange.isCenter) {
                 this.props.inverse();
             } else {
                 this.props.center();
             }
             e.preventDefault();
             e.stopPropagation();
        }
    
        render() {
             var ControllerUnitClassName = 'controllerUnit';
             if (this.props.arrange.isCenter) {
                 ControllerUnitClassName += ' is-center';
             }
    
             if (this.props.arrange.isInverse) {
                 ControllerUnitClassName += ' is-inverse';
             }
    
        return (
                 <span className={ControllerUnitClassName} onClick={this.handleClick}></span>
            );
        }
    }
    
    //背景舞台组件
    class Stage extends React.Component {
        constructor(props) {
            super(props);
            this.state = {
                //构建图片信息数组包括位置,角度,是否翻转和居中
                imgInfoArr: [
                    {
                        pos: {
                            left: '0',
                            top: '0'
                        },
                        rotate:0,
                        isInverse:false,
                        isCenter:false
                    }
                ]    
            };
            //内部变量:记录中心位置、水平垂直方向取值范围
            this.Constant = {
                    centerPos: {
                        left: 0,
                        top: 0
                    },
                    hPosRange: { //水平方向取值范围
                        leftSecX: [0, 0],
                        rightSecX: [0, 0],
                        y: [0, 0]
                    },
                    vPosRange: { //垂直方向取值范围
                        x: [0, 0],
                        topY: [0, 0]            
                    }
                };
            }
    
        //组件加载以后,为每张图片加载位置的范围
        componentDidMount() {
            //分别获取舞台和图片组件的宽高
            var stageDOM = ReactDOM.findDOMNode(this.refs.stage),
                stageH = stageDOM.scrollHeight,
                stageW = stageDOM.scrollWidth,
                halfStageH = Math.ceil(stageH / 2),
                halfStageW = Math.ceil(stageW / 2);
    
            var imgFigureDOM = ReactDOM.findDOMNode(this.refs.imgFigure0),
                imgH = imgFigureDOM.scrollHeight,
                imgW = imgFigureDOM.scrollWidth,
                halfImgH = Math.ceil(imgH / 2),
                halfImgW = Math.ceil(imgW / 2);
    
        this.Constant = {
            //计算取值范围并赋值给Constant
                centerPos: {
                    left: halfStageW - halfImgW,
                    top: halfStageH - halfImgH + 50
                },
                hPosRange: { //水平方向取值范围
                    leftSecX: [-halfImgW, halfStageW - halfImgW * 3],
                    rightSecX: [halfStageW + halfImgW, stageW - halfImgW],
                    y: [-halfImgH, stageH - halfImgH]
                },
                vPosRange: { //垂直方向取值范围
                    x: [halfStageW - imgW, halfStageW],
                    topY: [-halfImgH, halfStageH - halfImgH * 3]            
                }
            };
        //加载完成后进行布局
        this.setPosition(0);

    }
    //获取low、high之间的随机值
    getRandomValue(low, high) {
        return Math.ceil(Math.random() * (high - low) + low);
    }
    //获取正负30之间的随机值
    get30DeRandom() {
        return (Math.random() > 0.5 ? "" : '-') + Math.ceil(Math.random() * 30);
    }
    //翻转
    inverse(index) {
        return function () {
            var imgInfoArr = this.state.imgInfoArr;
            imgInfoArr[index].isInverse = !imgInfoArr[index].isInverse;
            this.setState({
                imgInfoArr:imgInfoArr
            });
        }.bind(this);
    }
    //居中
    center(index) {
        return () => {
            this.setPosition(index);
        }
    }
    //设置图片位置,centerIndex是居中图片的索引值
    setPosition(cneterIndex) {
        var centerPos = this.Constant.centerPos,
            hLeftXMin = this.Constant.hPosRange.leftSecX[0],
            hLeftXMax = this.Constant.hPosRange.leftSecX[1],
            hRightXMin = this.Constant.hPosRange.rightSecX[0],
            hRightXMax = this.Constant.hPosRange.rightSecX[1],
            hYMin = this.Constant.hPosRange.y[0],
            hYMax = this.Constant.hPosRange.y[1],
            vXMin = this.Constant.vPosRange.x[0],
            vXMax = this.Constant.vPosRange.x[1],
            vYMin = this.Constant.vPosRange.topY[0],
            vYMax = this.Constant.vPosRange.topY[1];

        var imgInfoArr = this.state.imgInfoArr;
        //获取居中图片并设置其值
        var cneterArr = imgInfoArr.splice(cneterIndex, 1);
        cneterArr[0].pos = centerPos;
        cneterArr[0].rotate = 0;
        cneterArr[0].isCenter = true;

        //获取在上方的图片(0或1个)
        var topImgNum = Math.floor(Math.random() * 2);
        var topIndex = Math.ceil(Math.random() * (imgInfoArr.length - topImgNum));
        var topArr = imgInfoArr.splice(topIndex, topImgNum);
            topArr.forEach(function(element, index) {
            topArr[index] = {
                pos: {
                    left: this.getRandomValue(vXMin, vXMax),
                    top: this.getRandomValue(vYMin, vYMax)
                },
                rotate:this.get30DeRandom(),
                isCenter:false,
            }
        }.bind(this));
        //将剩下的图片对半分放于左右两边
        for (let i = 0, j = imgInfoArr.length, k = j / 2; i < j; i++) {
            var hPosLOrR = null;
            if (i < k) {
                hPosLOrR = this.getRandomValue(hLeftXMin, hLeftXMax);
            } else {
                hPosLOrR = this.getRandomValue(hRightXMin, hRightXMax);
            }
            imgInfoArr[i] = {
                pos: {
                    left:hPosLOrR,
                    top:this.getRandomValue(hYMin, hYMax)
                },
                rotate: this.get30DeRandom(),
                isCenter:false,
            };
        }
        //按顺序将顶部与中心图片数据传回数组
        if (topArr && topArr[0]) {
            imgInfoArr.splice(topIndex, 0, topArr[0]);
        }
        imgInfoArr.splice(cneterIndex, 0, cneterArr[0]);

        this.setState({
            imgInfoArr:imgInfoArr
        });
    }

    render() {
        var imgs = [];
        var controllers = [];
        Data.forEach( function(value, index) {
            //初始化图片数据数组,不能用setState方法,否则每次都会触发渲染
            if (!this.state.imgInfoArr[index]) {
                this.state.imgInfoArr[index] = {
                     pos: {
                         left: 0,
                          top: 0
                      },
                      rotate: 0,
                      isInverse: false,
                      isCenter:false        
                }
              }
            value.imgUrl = require('./images/' + value.fileName);
            imgs.push(<ImgFigure arrange={this.state.imgInfoArr[index]} key={index} ref={'imgFigure' + index} data={value} inverse={this.inverse(index)} center={this.center(index)}/>);
            controllers.push(<ControllerUnit key={index} arrange={this.state.imgInfoArr[index]} inverse={this.inverse(index)} center={this.center(index)} />)
        }.bind(this));

        return (
            <section className="stage" ref='stage'>
                <section className="imgStage">{imgs}</section>
                <nav className="controller">{controllers}</nav>
            </section>
        );
    }
}

ReactDOM.render(<Stage />, document.getElementById('root'));  
  • index.css

    @font-face {font-family: "iconfont";
      src: url("./fonts/icons/iconfont.eot") format("embedded-opentype"), url("./fonts/icons/iconfont.woff") format("woff"), url("./fonts/icons/iconfont.ttf") format("truetype"), url("./fonts/icons/iconfont.svg") format("svg");
    }
    
    html, body {
      margin: 0;
      padding: 0;
      font-family: sans-serif;
      background-color: #ddd;
    }
    
    #root {
        width: 100%;
        height: 100%;
    }
    /*Stage start*/
    .stage {
        position: relative;
        width: 100%;
        height: 828px;
    
    }
    
    .imgStage {
        position: relative;
        width: 100%;
        height: 100%;
        overflow: auto;
        background-color: #ddd;
        perspective: 1800px;
        overflow: hidden;
    }
    /*Stage end*/
    /*imgFigure start*/
    .img-figure {
        position: absolute;
        height: 347px;
        width: 480px;
        margin: 0;
        padding: 40px;
        box-sizing: border-box;
        background-color: #FFF;
        cursor: pointer;
        transform-style: preserve-3d;
        transition: left .6s ease-in-out, top .6s ease-in-out, transform .6s ease-in-out;
        transform-origin: 0 50% 0;
    }
    .img-figure.is-inverse {
        transform: translate(480px) rotateY(180deg);
    }
    .img-back {
        position: absolute;
        left: 0;
        top: 0;
        width: 100%;
        height: 100%;
        padding: 40px;
        overflow: auto;
        color: #a7a0a2;
        font-size: 22px;
        line-height: 1.25;
        text-align: left;
        background-color: #FFF;
        box-sizing: border-box;
    
        backface-visibility: hidden;
        transform: rotateY(180deg);
    }
    .img-back p {
        margin: 0;
    }
    figcaption {
        text-align: center;
    }
    .img-title {
        margin-top: 10px;
        color: #a7a0a2;
        font-size: 16px;
    }
    /*imgFigure end*/
    /*controllerUnit start*/
    .controller {
        position: absolute;
        left: 0;
        bottom: 30px;
        z-index: 101;
        width: 100%;
        text-align: center;
    }
    .controllerUnit {
        display: inline-block;
        width: 30px;
        height: 30px;
        margin: 0 5px;
        text-align: center;
        cursor: pointer;
        background-color: #aaa;
        border-radius: 50%;
        transform: scale(.5);
        vertical-align: middle;
        transition: transform .6s ease-in-out, background-color .3s;
    }
    .controllerUnit.is-center {
        background-color: #888; 
        transform: scale(1);
    }
    .controllerUnit.is-center::after {
        font-family: 'iconfont';
        content: "\e515";
        color: #fff;
        line-height: 30px;
        font-size: 80%;
    }
    .controllerUnit.is-center.is-inverse {
        background-color: #555;
        transform: rotateY(180deg);
    }
    /*controllerUnit end*/
    

React井字棋

发表于 2017-12-07

井字棋

React官方文档示例WebApp

三个组件

  • Square:棋盘的一小格
  • Board:棋盘
  • Game:游戏信息的显示和过程的监控

其中Square、Board是展示组件不负责数据交互,Game是容器组件,负责数据交互。

实现功能

  1. 轮流下子
  2. 判断胜负
  3. 历史纪录
  4. 历史纪录升降序转换
  5. 高亮显示获胜行

代码

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';



function Square(props) {
  if (props.highlight) {
    return (
      <button className="square" onClick={() => props.onClick()} style={{color: "red"}}> 
        {props.value}
      </button>
    );
  }else {
  return (
    <button className="square" onClick={() => props.onClick()}>
      {props.value}
    </button>
  );
  }
}

Square组件:对其属性highligt进行判断,来决定颜色是否为红色。按钮的值是他的value属性。同时还有一个onclick方法继承自父组件。

class Board extends React.Component {
  renderSquare(i) {
    return (
      <Square
        value={this.props.squares[i]}
        onClick={() => this.props.onClick(i)}
        highlight={this.props.line.includes(i)}
      />
    );
  }

  render() {
      var rows = [];
      for (var i = 0; i < 3; i++) {
          var row = [];
          for (var j = 3 * i; j < 3 * i + 3; j++) {
              row.push(this.renderSquare(j));
          }
          rows.push(<div className='board-row'>{row}</div>)
      }
    return (
      <div>
        {rows}
      </div>
    );
  }
}

Board组件:使用renderSquare(i)方法来创建Square组件,其value值是Board组件square[i],onclick是继承自Board组件的方法,highlight是判断Board组件的line中是否有i,render方法中用两个for循环来渲染出九宫格。

class Game extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      history: [
        {
          squares: Array(9).fill(null),
          lastPos:'Get Game Start'
        }
      ],
      stepNumber: 0,
      xIsNext: true,
      sort:true
    };
  }

  handleClick(i) {
    const history = this.state.history.slice(0, this.state.stepNumber + 1);
    const current = history[history.length - 1];
    const squares = current.squares.slice();
    const posX = Math.ceil((i+1) / 3);
    const posY = (i + 1) % 3 ? (i + 1) % 3 : 3;
    const position = '(' + posX + ', ' + posY + ')';
    if (calculateWinner(squares).winner || squares[i]) {
      return;
    }
    squares[i] = this.state.xIsNext ? "X" : "O";
    this.setState({
      history: history.concat([
        {
          squares: squares,
          lastPos:squares[i] + ' To ' + position
        }
      ]),
      stepNumber: history.length,
      xIsNext: !this.state.xIsNext,
    });
  }

  jumpTo(step) {
    this.setState({
      stepNumber: step,
      xIsNext: (step % 2) === 0
    });
  }

   toggleSort() {
    this.setState({
      sort:!this.state.sort,
    })
  }

  render() {
    let history = this.state.history;
    const current = history[this.state.stepNumber];
    const winner = calculateWinner(current.squares).winner;
    const line = calculateWinner(current.squares).line;
    if (!this.state.sort){
        history = this.state.history.slice();
          history.reverse();
      }
    const moves = history.map((step, move) => {
      const desc = step.lastPos;
      if (this.state.stepNumber == move) {
          return (
        <li key={move}>
          <button onClick={() => this.jumpTo(move)}><strong>{desc}</strong></button>
        </li>
        );
      } else {
          return (
        <li key={move}>
          <button onClick={() => this.jumpTo(move)}>{desc}</button>
        </li>
        );
      }  
    });

    let status;
    if (winner) {
      status = "Winner: " + winner;
    } else {
      status = "Next player: " + (this.state.xIsNext ? "X" : "O");
    }

    return (
      <div className="game">
        <div className="game-board">
          <Board
            squares={current.squares}
            onClick={i => this.handleClick(i)}
            line={line}
          />
        </div>
        <div className="game-info">
         <button onClick={() => this.toggleSort()}>Sort</button>
          <div>{status}</div>
          <ol>{moves}</ol>
        </div>
      </div>
    );
  }
}

Game组件:首先看他的状态(state),一个是history数组,其中每个元素是一个对象,对象里有一个记录着棋盘形态的数组和这一步走的位置;stepNumber,记录走的步数;xIsNext判断轮到谁下子;sort判断升降序。
handleClick()方法:是最终传入Square的方法,它获得了历史纪录和这步没走之前棋局,如果已经获胜或这格已经落子,就直接返回,没有的话就计算他的位置并更新history,stepNumber,和xIsNext
render()中获取历史纪录和当前的棋局,计算胜者和获胜的棋子,判断是否需要升降序,moves对数组history进行map,如果stepNumber与索引相等,就返回加粗显示的列表,否则返回普通列表(这里注意列表项要有key值)。jumpTo()方法接受步数,并将stepNumber设置为参数,并更新xIsNext。下面通过status来显示游戏状态,最终渲染Board组件,升降序按钮,状态显示,历史记录列表。

// ========================================

ReactDOM.render(<Game />, document.getElementById("root"));

function calculateWinner(squares) {
  const lines = [
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8],
    [0, 3, 6],
    [1, 4, 7],
    [2, 5, 8],
    [0, 4, 8],
    [2, 4, 6]
  ];
  for (let i = 0; i < lines.length; i++) {
    const [a, b, c] = lines[i];
    if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
      return {winner:squares[a], line:[a, b, c]};
    }
  }
  return {winner:null, line:[]};
}

总结

初次使用React开发,感觉难度是有的,一些细节了解的也不是很清楚,但是组件化开发显而易见的好处就是思路清晰,各组件联系紧密又各司其职。代码逻辑性很强也有利于后期维护和功能的升级。

Android强制下线功能

发表于 2017-08-31

文件目录

content

创建ActivityCollector类用于管理所有活动

package com.example.broadcastbestpratice;

import android.app.Activity;

import java.util.ArrayList;
import java.util.List;

public class ActivityCollector {
    public static List<Activity> activities = new ArrayList<>();

    public static void addActivity(Activity activity) {
        activities.add(activity);
    }

    public static void removeActivity(Activity activity) {
        activities.remove(activity);
    }

    public static void finishAll() {
        for (Activity activity : activities) {
            if (!activity.isFinishing()) {
                activity.finish();
            }
        }
        activities.clear();
    }
}
  • 强制下线功能需要关闭所有活动回到登录界面

创建BaseActivity类作为所有活动的父类

package com.example.broadcastbestpratice;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;

public class BaseActivity extends AppCompatActivity {
    private ForceOfflineReceiver receiver;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityCollector.addActivity(this);
    }

    @Override
    protected void onPostResume() {
        super.onPostResume();
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction("com.example.broadcastbestpratice.FORCE_OFFLINE");
        receiver = new ForceOfflineReceiver();
        registerReceiver(receiver,intentFilter);
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (receiver != null) {
            unregisterReceiver(receiver);
        }
        receiver = null;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ActivityCollector.removeActivity(this);
    }

    class ForceOfflineReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(final Context context, Intent intent) {
            AlertDialog.Builder builder = new AlertDialog.Builder(context);
            builder.setTitle("Warning");
            builder.setMessage("You are forced to be offline.Please try to login     again");
            builder.setCancelable(false);
            builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    ActivityCollector.finishAll();
                    Intent intent = new Intent(context, LoginActivity.class);
                    context.startActivity(intent);
                }
            });
            builder.show();
        }
    }
}
  • 在BaseActivity动态注册广播接收器
  • onReceive()中弹出对话框,点击OK后销毁所有活动并启动LoginActivity
  • 重写了onPostResume()和onPause()方法,在其中分别注册和取消广播接收,因为只有活动在栈顶时我们才需要强制下线功能

编写登陆布局(activity_login.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="60dp">
        <TextView
            android:layout_width="90dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:textSize="18sp"
            android:text="Account:"/>

        <EditText
            android:id="@+id/account"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_gravity="center_vertical"/>
    </LinearLayout>

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="60dp">
        <TextView
            android:layout_width="90dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:textSize="18sp"
            android:text="Password:"/>

        <EditText
            android:id="@+id/password"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_gravity="center_vertical"
            android:inputType="textPassword"/>

    </LinearLayout>

    <Button
        android:id="@+id/login"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:text="Login"/>

</LinearLayout>

修改LoginActivity

package com.example.broadcastbestpratice;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class LoginActivity extends BaseActivity {

    private EditText accountEdit;
    private EditText passwordEdit;
    private Button login;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        accountEdit = (EditText) findViewById(R.id.account);
        passwordEdit = (EditText) findViewById(R.id.password);
        login = (Button) findViewById(R.id.login);
        login.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String account = accountEdit.getText().toString();
                String password = passwordEdit.getText().toString();

                if (account.equals("admin") && password.equals("123456")) {
                    Intent intent = new Intent(LoginActivity.this, MainActivity.class);
                    startActivity(intent);
                    finish();
                } else {
                    Toast.makeText(LoginActivity.this,"account or password is invalid",Toast.LENGTH_SHORT).show();
                }
            }
        });
    }
}
  • 在登陆按钮点击事件中只有输入正确的账户密码才能登陆并跳转至MainActivity

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.broadcastbestpratice.MainActivity">

    <Button
        android:id="@+id/force_offline"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Send force offline broadcast"/>

</android.support.constraint.ConstraintLayout>
  • 只需要一个按钮来实现强制下线

MainActivity

package com.example.broadcastbestpratice;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends BaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button forceOffline = (Button) findViewById(R.id.force_offline);
        forceOffline.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent("com.example.broadcastbestpratice.FORCE_OFFLINE");
                sendBroadcast(intent);
            }
        });
    }
}
  • 在按钮点击事件里发送了一条广播,值为com.example.broadcastbestpratice.FORCE_OFFLINE,这条广播就是用于通知程序强制下线的

将LoginActivity改为主活动

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.broadcastbestpratice">

    <!-- To auto-complete the email text field in the login form with the user's emails -->
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    <uses-permission android:name="android.permission.READ_PROFILE" />
    <uses-permission android:name="android.permission.READ_CONTACTS" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">

        </activity>
        <activity android:name=".LoginActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

login

warning

Android简易新闻客户端

发表于 2017-08-30

文件目录

news

在app/build.gradle当中添加依赖库

apply plugin: 'com.android.application'

android {
    compileSdkVersion 24
    buildToolsVersion "26.0.1"
    defaultConfig {
        applicationId "com.example.fragmentbestpractice"
        minSdkVersion 15
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:24.2.1'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    compile 'com.android.support:recyclerview-v7:24.2.1'//recyclerview库
    testCompile 'junit:junit:4.12'
}

新闻的实体类

package com.example.fragmentbestpractice;

public class News {
    private String title;
    private String content;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }
    public void setContent(String content) {
        this.content = content;
    }
}

新闻内容的布局(news_content_frag.xml)

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:id="@+id/visibility_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:visibility="invisible">

        <TextView
            android:id="@+id/news_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:padding="10dp"
            android:textSize="20sp"/>

        <View
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:background="#000"/>

        <TextView
            android:id="@+id/news_content"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:padding="15dp"
            android:textSize="18sp"/>
    </LinearLayout>

    <View
        android:layout_width="1dp"
        android:layout_height="match_parent"
        android:layout_alignParentLeft="true"
        android:background="#000"/>

</RelativeLayout>

新闻内容的布局类(NewsContentFragment)

package com.example.fragmentbestpractice;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;


public class NewsContentFragment extends Fragment {

    private View view;


    @Override
    public View onCreateView(LayoutInflater inflater,  ViewGroup container,  Bundle savedInstanceState) {
        view = inflater.inflate(R.layout.news_content_frag,container,false);
        return view;
    }

    public void refresh(String newsTitle, String newsContent) {
        View visibilityLayout = view.findViewById(R.id.visibility_layout);
        visibilityLayout.setVisibility(View.VISIBLE);
        TextView newsTitleText = (TextView) view.findViewById(R.id.news_title);
        TextView newsContentText = (TextView) view.findViewById(R.id.news_content);
        newsTitleText.setText(newsTitle);
        newsContentText.setText(newsContent);
    }
}
  • onCreateView()方法里加载了刚才创建的布局
  • refresh()方法将新闻的标题和内容显示出来

创建单页使用的活动(NewsContentActivity)并修改activity_news_content.xml

<?xml version=”1.0” encoding=”utf-8”?>





* 直接在布局中引用了NewsContentFragment,也就相当于把news_content_frag布局引用了进来


修改单页使用的活动(NewsContentActivity)

package com.example.fragmentbestpractice;

import android.content.Context;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class NewsContentActivity extends AppCompatActivity {

    public static void actionStart(Context context, String newsTitle, String newsContent) {
        Intent intent = new Intent(context,NewsContentActivity.class);
        intent.putExtra("news_title", newsTitle);
        intent.putExtra("news_content", newsContent);
        context.startActivity(intent);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_news_content);
        String newsTitle = getIntent().getStringExtra("news_title");
        String newsContent = getIntent().getStringExtra("news_content");
        NewsContentFragment newsContentFragment = (NewsContentFragment) getSupportFragmentManager().findFragmentById(R.id.news_content_fragment);
        newsContentFragment.refresh(newsTitle, newsContent);
    }
}
  • onCreate()方法中通过Intent获取到了传入的新闻标题和新闻内容,然后调用了FragmentManager的findFragmentById()方法得到了NewsContentFragment的实例,接着调用它的refresh()方法来传入新闻标题和内容。
  • actionStart()方法使Intent传递的数据一目了然

创建用于显示新闻列表的布局(news_title_frag.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/news_title_recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>
  • 里面只有一个显示新闻列表的RecyclerView

创建RecyclerView子项的布局(news_item.xml)

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/news_title"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:maxLines="1"
    android:ellipsize="end"
    android:textSize="18sp"
    android:paddingLeft="10dp"
    android:paddingRight="10dp"
    android:paddingTop="15dp"
    android:paddingBottom="15dp"/>
  • android:ellipsize用于指定当文本内容超出控件宽度时,文本的缩略方式,end表示在尾部进行缩略。

创建NewsTitleFragment作为展示新闻列表的碎片

package com.example.fragmentbestpractice;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;


public class NewsTitleFragment extends Fragment {
    private boolean isTwoPane;


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,  Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.news_title_frag, container, false);
        RecyclerView newsTitleRecyclerView = (RecyclerView) view.findViewById(R.id.news_title_recycler_view);
        LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
        newsTitleRecyclerView.setLayoutManager(layoutManager);
        NewsAdapter adapter = new NewsAdapter(getNews());
        newsTitleRecyclerView.setAdapter(adapter);
        return view;
    }    

    private List<News> getNews() {
        List<News> newsList = new ArrayList<>();
        for (int i = 1; i <= 50; i++) {
            News news = new News();
            news.setTitle("This is news title" + i);
            news.setContent(getRandomLengthContnet("This is news contnet" + i + "."));
            newsList.add(news);
        }
        return newsList;
    }

    private String getRandomLengthContnet(String content) {
        Random random = new Random();
        int length = random.nextInt(20) + 1;
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < length; i++) {
            builder.append(content);
        }
        return builder.toString();
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if (getActivity().findViewById(R.id.news_content_layout) != null) {
            isTwoPane = true;
        } else {
            isTwoPane = false;
        }
    }

    class NewsAdapter extends RecyclerView.Adapter<NewsAdapter.ViewHolder> {
        private List<News> mNewsList;

        class ViewHolder extends RecyclerView.ViewHolder {
            TextView newsTitleText;

            public ViewHolder(View view) {
                super(view);
                newsTitleText = (TextView) view.findViewById(R.id.news_title);
            }
        }

        public NewsAdapter(List<News> newsList) {
            mNewsList = newsList;
        }

        @Override
        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.news_item,parent,false);
            final ViewHolder holder = new ViewHolder(view);
            view.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    News news = mNewsList.get(holder.getAdapterPosition());
                    if (isTwoPane) {
                        NewsContentFragment newsContentFragment = (NewsContentFragment) getFragmentManager().findFragmentById(R.id.news_content_fragment);
                        newsContentFragment.refresh(news.getTitle(),news.getContent());
                    } else {
                        NewsContentActivity.actionStart(getActivity(),news.getTitle(),news.getContent());
                    }
                }
            });

            return holder;
        }

        @Override
        public void onBindViewHolder(ViewHolder holder, int position) {
            News news = mNewsList.get(position);
            holder.newsTitleText.setText(news.getTitle());
        }

        @Override
        public int getItemCount() {
            return mNewsList.size();
        }
    }


}    
  • onCreateView()方法中加载了news_title_frag布局和RecyclerView的使用
  • onActivityCreated()这个方法通过判断在活动中是否能找到news_content_layout布局来确定加载的单/双页模式,我们让news_content_layout在双页模式中才出现
  • 将适配器写成内部类可以直接访问NewsTitleFragment的内部变量
  • onCreateViewHolder()中注册的点击事件,先判断单双页,如果是单页,就启动新的活动,如果是双页,就更新新闻内容碎片里的数据。
  • Random类的public int nextInt(int n)方法:该方法的作用是生成一个随机的int值,该值介于[0,n)的区间,也就是0到n之间的随机int值,包含0而不包含n。
  • String对象是不可改变的。每次使用 System.String类中的方法之一时,都要在内存中创建一个新的字符串对象,这就需要为该新对象分配新的空间。在需要对字符串执行重复修改的情况下,与创建新的 String对象相关的系统开销可能会非常昂贵。如果要修改字符串而不创建新的对象,则可以使用System.Text.StringBuilder类。例如,当在一个循环中将许多字符串连接在一起时,使用 StringBuilder类可以提升性能。

实现单双页(修改activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/news_title_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
        android:id="@+id/news_title_fragment"
        android:name="com.example.fragmentbestpractice.NewsTitleFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</FrameLayout>
  • 单页模式下,加载一个新闻标题的碎片

实现单双页(新建layout-sw600dp\activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
        android:id="@+id/news_title_fragment"
        android:name="com.example.fragmentbestpractice.NewsTitleFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"/>

    <FrameLayout
        android:id="@+id/news_content_layout"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="3">

        <fragment
            android:id="@+id/news_content_fragment"
            android:name="com.example.fragmentbestpractice.NewsContentFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    </FrameLayout>

</LinearLayout>
  • 在双页模式中同时引入两个碎片,并将新闻内容的碎片放在了id为news_content_layout的FrameLayout布局下

效果图

pad


mob1

mob2

Android编写聊天界面

发表于 2017-08-30

文件目录

ui目录

在app下的build.gradle中添加依赖库(RecyclerView)

apply plugin: 'com.android.application'

android {
    compileSdkVersion 24
    buildToolsVersion "26.0.1"
    defaultConfig {
        applicationId "com.example.uibestpractice"
        minSdkVersion 15
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguarrules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:24.2.1'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    compile 'com.android.support:recyclerview-v7:24.2.1'//添加RecyclerView依赖库
    testCompile 'junit:junit:4.12'
}

编写主界面(activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#d8e0d8">

<android.support.v7.widget.RecyclerView
    android:id="@+id/msg_recycler_view"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="1"/>

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <EditText
        android:id="@+id/input_text"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:hint="Type something here"
        android:maxLines="2"/>

    <Button
        android:id="@+id/send"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Send"/>

</LinearLayout>

</LinearLayout> 
  • 在主界面中放置的RecyclerView用于显示消息
  • EditText用于编辑消息
  • Button用于发送消息

定义消息的实体类Msg

package com.example.uibestpractice;

public class Msg {
    public static final int TYPE_RECEIVED = 0;
    public static final int TYPE_SENT = 1;
    private String content;
    private int type;

    public Msg(String content,int type) {
        this.content = content;
        this.type = type;
    }

    public String getContent() {
        return content;
    }
    public int getType() {
        return type;
    }
}
  • 用两个常量来表示消息的类型(接收的还是发送的)

编写RecyclerView的子布局(msg_item.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="10dp">

    <LinearLayout
        android:id="@+id/left_layout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="left"
        android:background="@drawable/message_left">

        <TextView
            android:id="@+id/left_msg"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_margin="10dp"
            android:textColor="#fff"/>
    </LinearLayout>

    <LinearLayout
        android:id="@+id/right_layout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="right"
        android:background="@drawable/message_right">

        <TextView
            android:id="@+id/right_msg"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_margin="10dp"/>
    </LinearLayout>

</LinearLayout>
  • 将接收的消息居左对齐,发送的消息居右对齐

创建RecyclerView适配器类

package com.example.uibestpractice;

import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;

import java.util.List;

public class MsgAdapter extends RecyclerView.Adapter<MsgAdapter.ViewHolder>{
    private List<Msg> mMsgList;

    static class ViewHolder extends RecyclerView.ViewHolder {
        LinearLayout leftLayout;
        LinearLayout rightLayout;
        TextView leftMsg;
        TextView rihgtMsg;

        public ViewHolder(View view) {
            super(view);
            leftLayout = (LinearLayout) view.findViewById(R.id.left_layout);
            rightLayout = (LinearLayout) view.findViewById(R.id.right_layout);
            leftMsg = (TextView) view.findViewById(R.id.left_msg);
            rihgtMsg = (TextView) view.findViewById(R.id.right_msg);
        }
    }

    public MsgAdapter(List<Msg> msgList) {
        mMsgList = msgList;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.msg_item,parent,false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        Msg msg = mMsgList.get(position);
        if (msg.getType() == Msg.TYPE_RECEIVED) {
            holder.leftLayout.setVisibility(View.VISIBLE);
            holder.rightLayout.setVisibility(View.GONE);
            holder.leftMsg.setText(msg.getContent());
        } else if (msg.getType() == Msg.TYPE_SENT) {
            holder.rightLayout.setVisibility(View.VISIBLE);
            holder.leftLayout.setVisibility(View.GONE);
            holder.rihgtMsg.setText(msg.getContent());
        }
    }

    @Override
    public int getItemCount() {
        return mMsgList.size();
    }
}
  • 定义了一个内部类ViewHolder,继承自RecyclerView.ViewHolder。ViewHolder的构造函数中传入一个View参数,这个参数通常是RecyclerView子项的最外层布局,这样我们就可以通过findViewById()方法来获取布局中的接收和发送消息布局的实例了。
  • MsgAdapter中也有一个构造函数,将要展示的数据源传进来复制给mMsgList。
  • MsgAdapter继承自RecyclerView.Adapter,必须重写onCreateViewHolder()、onBindViewHolder()、getItemCount()三个方法。
  • onCreateViewHolder()用于创建ViewHolder实例,在这个方法中将msg_item布局加载进来,然后创建一个ViewHolder实例,并把加载出来的布局传到构造函数中,返回实例。
  • onBindViewHolder()用于对RecyclerView子项的数据进行赋值。
  • getItemCount()获得RecyclerView有多少个子项

使用RecyclerView(修改MainActivity)

package com.example.uibestpractice;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    private List<Msg> msgList = new ArrayList<>();
    private EditText inputText;
    private Button send;
    private RecyclerView msgRecyclerView;
    private MsgAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initMsgs();
        inputText = (EditText) findViewById(R.id.input_text);
        send = (Button) findViewById(R.id.send);
        msgRecyclerView = (RecyclerView) findViewById(R.id.msg_recycler_view);
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        msgRecyclerView.setLayoutManager(layoutManager);
        adapter = new MsgAdapter(msgList);
        msgRecyclerView.setAdapter(adapter);
        send.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String content = inputText.getText().toString();
                if (!"".equals(content)) {
                    Msg msg = new Msg(content,Msg.TYPE_SENT);
                    msgList.add(msg);
                    adapter.notifyItemInserted(msgList.size()-1);
                    msgRecyclerView.scrollToPosition(msgList.size()-1);
                    inputText.setText("");
                }
            }
        });
    }

    private void initMsgs() {
        Msg msg1 = new Msg("Hello",Msg.TYPE_RECEIVED);
        msgList.add(msg1);
        Msg msg2 = new Msg("I'm John",Msg.TYPE_RECEIVED);
        msgList.add(msg2);
        Msg msg3 = new Msg("Hello",Msg.TYPE_SENT);
        msgList.add(msg3);
    }
}
  • onCreate()方法中先获得了RecyclerView的实例,然后创建了LinearLayoutManager对象,并把它设置到RecyclerView的实例中去。LayoutManager用于指定RecyclerView的布局方式,这里使用是线性布局的意思,可以实现ListView相同的效果。
  • 设置了send按钮的响应事件,如果内容不为空则创建出一个新的Msg对象,并添加到msgList中去,之后调用了适配器的方法notifyItemInserted()来通知列表有新数据插入,这样新增的消息才能在RecyclerView中显示。接着调用RecyclerView的scrollToPosition()方法,将显示的数据定位到最后一行,最后清空输入栏。

ui效果图

Java集合与泛型

发表于 2017-08-19

Java几个常用的集合

  • ArrayList
  • TreeSet:以有序状态保持并可防止重复
  • HashMap:可用成对的name/value来保存与取出
  • LinkedList:针对经常插入或删除中间元素所设计的高效率集合
  • HashSet:防止重复的集合,可快速的找寻相符的元素
  • LinkedHashMap:类似HashMap,但可以记住元素插入的顺序,也可以设定成依照元素上次存取的先后来排序

用Collections.sort()来排序

Collections.sort()会把list中的String依照字母排序

import java.util.*;
import java.io.*;

public class JukeBox {

    ArrayList<String> songList = new ArrayList<String>();

    public static void main(String[] args) {
        new JukeBox().go();
    }

    public void go() {
        getSongs();
        System.out.println(songList);
        Collections.sort(songList);
        System.out.println(songList);
    }

    public void getSongs() {
        try {
            File file = new File("SongList.txt");
            BufferedReader reader = new BufferedReader(new FileReader(file));
            String line = null;
            while ((line = reader.readLine()) != null) {
                addSong(line);
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    void addSong(String line) {
        String[] tokens = line.split("/");
        songList.add(tokens[0]);
    }

}

输出:

SongList

泛型

几乎所有以泛型写的程序都与处理集合有关,它的主要目的是让集合有类型安全性。能防止把不同类的东西加到集合中。

关于泛型要知道的几个问题

  1. 创建被泛型化类(例如ArrayList)的实例。创建ArrayList时你必须要指定它所容许的对象。

    new ArrayList<Song>();
    
  2. 声明与指定泛型类型的变量

  3. 声明(与调用)取用泛型类型的方法

使用泛型的类

ArrayList的说明文件:

public class ArrayList<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable  

E部分会用你所声明与创建的真正类型来取代。

运用泛型的方法

  1. 使用定义在类声明的类型参数

    public class ArrayList
    extends AbstractList
    implements List, RandomAccess, Cloneable, Serializable

  2. 使用未定义在类声明的类型参数

    public void taking(ArrayList list)

这里的参数可以是Animal和他的子类。

自建类使用Collections.sort()

自己创建的类在使用sort()的时候,必须要实现Comparable接口。
要实现多种方式排序,可以用Comparator接口。

靠歌名来排序的Song类

public class Song implements Comparable<Song> {

    String title;
    String artist;

    public int compareTo(Song a) {//负数小于,零相等,正数大于
        return title.compareTo(a.getTitle());
    }

    Song (String t, String a) {
        title = t;
        artist = a;
    }

    public String getTitle() {
        return title;
    }

    public String getArtist() {
        return artist;
    }

    public String toString() {
        return title;
    }

}

加入歌星排序

import java.util.*;
import java.io.*;


public class JukeBox2 {

    ArrayList<Song> songList = new ArrayList<Song>();
    public static void main(String[] args) {
        new JukeBox2().go();
    }

    class ArtistCompare implements Comparator<Song> {//创建Comparator
        public int compare(Song a, Song b) {//实现比较方法
            return a.getArtist().compareTo(b.getArtist());
        }
    }

    public void go() {
        getSongs();
        System.out.println(songList);
        Collections.sort(songList);
        System.out.println(songList);

        ArtistCompare artistCompare  = new ArtistCompare();
        Collections.sort(songList, artistCompare );

        System.out.println(songList);
    }

    void getSongs() {
        try {
            File file = new File("SongList.txt");
            BufferedReader reader = new BufferedReader(new FileReader(file));
            String line = null;
            while ((line = reader.readLine()) != null) {
                addSong(line);
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    void addSong(String line) {
        String[] tokens = line.split("/");
        Song nextSong = new Song(tokens[0],tokens[1]);
        songList.add(nextSong);
    }

}    

输出:

artist

Collection

三个主要的接口

  • List:一种知道索引位置的集合
  • Set:注重独一无二的性质
  • Map:使用键值对

collection

HashSet

对象相等

  • 引用相等性:堆上同一对象的两个引用是相等的。
  • 对象相等性:堆上两个不同对象在意义上是相等的。

HashSet如何检查重复

把对象加入HashSet时,HashSet会使用对象的hashcode值来判断对象加入的位置。同时也会与其他已经加入的对象的hashcode做对比,如果hashcode不相同,HashSet会假设对象不相同,如果hashcode相同,就调用equals()来检查hashcode相等的对象是否真的相同。

public class Song implements Comparable<Song> {

    String title;
    String artist;

    public boolean equals(Object aSong) {//相等的判断方法
        Song s = (Song) aSong;
        return getTitle().equals(s.getTitle());
    }

    public int hashCode() {//hashCode的判断方法
        return title.hashCode();
    }

    public int compareTo(Song a) {//负数小于,零相等,正数大于
        return title.compareTo(a.getTitle());
    }

    Song (String t, String a) {
        title = t;
        artist = a;
    }

    public String getTitle() {
        return title;
    }

    public String getArtist() {
        return artist;
    }

    public String toString() {
        return title;
    }

}

import java.util.*;
import java.io.*;


public class JukeBox2 {

    ArrayList<Song> songList = new ArrayList<Song>();
    public static void main(String[] args) {
        new JukeBox2().go();
    }

    class ArtistCompare implements Comparator<Song> {//创建Comparator
        public int compare(Song a, Song b) {//实现比较的方法
            return a.getArtist().compareTo(b.getArtist());
        }
    }

    public void go() {
        getSongs();
        System.out.println(songList);
        Collections.sort(songList);
        System.out.println(songList);

        HashSet<Song> songSet = new HashSet<Song>();
        songSet.addAll(songList);//addAll()方法可以把其他集合的元素加进来
        System.out.println(songSet);

    }

    void getSongs() {
        try {
            File file = new File("SongList.txt");
            BufferedReader reader = new BufferedReader(new FileReader(file));
            String line = null;
            while ((line = reader.readLine()) != null) {
                addSong(line);
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    void addSong(String line) {
        String[] tokens = line.split("/");
        Song nextSong = new Song(tokens[0],tokens[1]);
        songList.add(nextSong);
    }

}

输出:

hashCode


规则

TreeSet

要使用TreeSet,下列其中一项必须为真:

  1. 集合中的元素必须是实现Comparable的类型。
  2. 使用重载、取用Comparator参数的构造函数来创建TreeSet。
import java.util.*;

public class TestTree {
    public static void main(String[] args) {
        new TestTree().go();
    }

    public void go() {
        Book b1 = new Book("How Cats Work");
        Book b2 = new Book("Remix Your Body");
        Book b3 = new Book("Finding Me");

        TreeSet<Book> tree1 = new TreeSet<Book>();

        tree1.add(b1);
        tree1.add(b2);
        tree1.add(b3);

        System.out.println(tree1);

        BookCompare bCompare = new BookCompare();
        TreeSet<Book> tree2 = new TreeSet<Book>(bCompare);

        tree2.add(b1);
        tree2.add(b2);
        tree2.add(b3);
        System.out.println(tree2);        

    }

    public class BookCompare implements Comparator<Book> {//定义到使用的类中
        public int compare(Book one, Book two) {
            return (two.title.compareTo(one.title));//降序排列
        }
    }
}

class Book implements Comparable {
    String title;
    public Book(String t) {
        title = t;
    }

    public int compareTo(Object b) {
        Book book = (Book) b;
        return title.compareTo(book.title);
    }

    public String toString() {
        return title;
    }

}

输出:

treeset

Map

import java.util.*;

public class TestMap {
    public static void main(String[] args) {

        HashMap<String, Integer> scores = new HashMap<String, Integer>();

        scores.put("KaKa", 56);
        scores.put("Henry", 100);
        scores.put("Matic", 61);

        System.out.println(scores);
        System.out.println(scores.get("Henry"));
    }
}

输出:

map

Java网络与线程

发表于 2017-08-16

网络

客户端要解决的三个问题

  1. 建立客户端与服务器之间的初始连接:用户通过socket连接来连接服务器。
  2. 用户送出信息给服务器。
  3. 用户从服务器接受信息。

建立Socket连接

Socket是个代表两台机器之间网络连接的对象(java.net.Socket)。要创建Socket连接要知道两项关于服务器的信息:IP地址和端口号。
Socket连接的建立代表两台机器之间存有对方的信息,包括网络地址和TCP的端口号。

使用BufferedReader从Socket上读取数据

用串流来通过Socket连接来沟通。

  1. 建立对服务器的Socket连接

    Socket chatSocket = new Socket("127.0.0.1", 5000);
    
  2. 建立连接到Socket上底层输入串流的InputStreamReader

    InputStreamReader stream = new InputStreamReader(chatSocket.getInputStream());
    
  3. 建立BufferedReader来读取

    BufferedReader reader = new BufferedReader(stream);
    String message = read.readline();
    

用PrintWriter写数据到Socket上

  1. 对服务器建立Socket连接

    Socket chatSocket = new Socket("127.0.0.1", 5000);
    
  2. 建立链接到Socket的PrintWriter

    PrintWriter writer = new PrintWriter(chatSocket.getInputStream());
    
  3. 写入数据

    writer.println("message to send");
    writer.print("anther message");
    

简单的客户端程序

import java.io.*;
import java.net.*;

public class DailyAdviceClient {

    public void go() {
        try {
            Socket s = new Socket("127.0.0.1", 4242);

            InputStreamReader streamReader = new InputStreamReader(s.getInputStream());
            BufferedReader reader = new BufferedReader(streamReader);

            String advice = reader.readLine();
            System.out.println("Today you should:" + advice);

            reader.close();
        } catch (IOException ex) {
            ex.getStackTrace();
        }
    }

    public static void main(String[] args) {
        DailyAdviceClient client = new DailyAdviceClient();
        client.go();
    }

}

编写简单的服务器程序

import java.io.*;
import java.net.*;

public class DailyAdviceServer {
    String[] adviceList = { "go to school", "go home", "go to a moive" };

    public void go() {
        try {
            ServerSocket serverSock = new ServerSocket(4242);//ServerSocket会监听客户端对这台机器4242端口上的要求

            //服务器进入循环等待服务客户端的请求
            while (true) {

                Socket sock = serverSock.accept(); //这个方法等待用户的时候停下来闲置,客户链接时返回一个Socket对象(与serverSock不同的端口)以便与客户端通信

                PrintWriter writer = new PrintWriter(sock.getOutputStream());
                String advice = adviceList[(int) (Math.random() * adviceList.length)];
                writer.println(advice);
                writer.close();
            }
        } catch (IOException ex) {
        ex.getStackTrace();
        }
    }
    public static void main(String[] args) {
        DailyAdviceServer server = new DailyAdviceServer();
        server.go();
    }
}

多线程

线程是独立的线程。它代表独立的执行空间。
Thread是Java中用来表示线程的类。要建立线程就要创建Thread。

启动新的线程

  1. 建立Runnable对象(线程的任务)

    Runnable threadJob = new MyRunnable();
    
  2. 建立Thread对象(执行者)并赋值Runnable(任务)

    Thread myThread = new Thread(threadJob);
    

    把Runnable对象传给Thread的构造函数。这会告诉Thread对象要把哪个方法放在执行空间去运行(Runnable的run()方法)

  3. 启动Thread

    myThread.start();
    

实现Runnable接口来建立给thread运行的任务

public class MyRunnable implements Runnable {
    public void run() {//必须要实现的方法,线程的任务
        go();
    }

    public void go() {
        doMore();
    }

    public void doMore() {
        System.out.println("In the thread");
    }

}

public class ThreadTester {

    public static void main(String[] args) {

        MyRunnable threadJob = new MyRunnable();
        Thread myThread = new Thread(threadJob);//将Runnable的实例传给Thread的构造函数

        myThread.start();//线程开始执行

        System.out.println("back in main");

    }

}

小结

  • 以小写t描述的thread是个独立的线程
  • Java中的每个线程都有独立的执行空间
  • 大写T的Thread是java.lang.Thread这个类。他的对象是用来表示线程
  • Thread需要任务,任务是实现过Runnable的实例
  • Runnable接口只有一个方法
  • run()会是新线程所执行的第一项方法
  • 要把Runnable传给Thread的构造函数才能启动新线程
  • 线程在初始化以后还没有调用start()之前处于新建立的状态
  • 调用Thread对象的start()之后,会建立出新的执行空间,它处于可执行状态等待被挑出来执行
  • 当Java虚拟机的调度器选择某个线程之后它就处于执行中的状态,单处理器的机器只能有一个执行中的线程
  • 有时线程会因为某些原因而被堵塞
  • 调度不能保证任何的执行时间和顺序,所以你不能期待它会完全的平均分配执行。

多线程

  • Thread.sleep()这个静态方法可以强制线程进入等待状态到过了设定的时间为止。
  • sleep()方法可能会抛出InterruptedException异常。
  • 可以用setName()方法帮线程命名,通常用来除错。
  • 如果两个以上的线程存取堆上相同的对象可能会出现严重的问题(数据的损毁)。
  • 要让对象在线程上有足够的安全性,就要判断出那些指令不能被分割执行。
  • 使用synchronized这个关键词修饰符可以防止两个线程同时进入同一对象的同一方法。
  • 每个对象都有单一的锁,单一的钥匙。这只会在对象带有同步化放方法时才有实际用途。
  • 线程尝试进入同步化过的方法时必须要取得对象的钥匙,如果已经被别的线程拿走了,那就得等。
  • 对象就算是有多个同步化过的方法,也还是只有一个锁。一旦某个线程进入该对象的同步化方法,其他线程就无法进入该对象的任何同步化方法。

简单的聊天软件程序

客户端

import java.io.*;
import java.net.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class SimpleChatClient {

    JTextArea incoming;
    JTextField outgoing;
    BufferedReader reader;
    PrintWriter writer;
    Socket sock;

    public static void main(String[] args) {
        SimpleChatClient client = new SimpleChatClient();
        client.go();
    }

    public void go() {

        JFrame frame = new JFrame("Chat Client");
        JPanel mainPanel = new JPanel();
        incoming = new JTextArea(15,50);
        incoming.setLineWrap(true);
        incoming.setWrapStyleWord(true);
        incoming.setEditable(false);

        JScrollPane qScroller = new JScrollPane(incoming);
        qScroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
        qScroller.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);

        outgoing = new JTextField(20);
        JButton sendButton = new JButton("Send");
        sendButton.addActionListener(new SendButtonListener());

        mainPanel.add(qScroller);
        mainPanel.add(outgoing);
        mainPanel.add(sendButton);

        setUpNetworking();

        Thread readerThread = new Thread(new IncomingReader());
        readerThread.start();

        frame.getContentPane().add(BorderLayout.CENTER, mainPanel);
        frame.setSize(400, 500);
        frame.setVisible(true);
    }

    private void setUpNetworking() {

        try {
            sock = new Socket("127.0.0.1", 4242);
            InputStreamReader streamReader = new InputStreamReader(sock.getInputStream());
            reader = new BufferedReader(streamReader);
            writer = new PrintWriter(sock.getOutputStream());
            System.out.println("networking established");
        } catch (IOException ex) {
            ex.printStackTrace();
        }        
    }

    public class SendButtonListener implements ActionListener {
        public void actionPerformed(ActionEvent ev) {
            try {
                writer.println(outgoing.getText());
                writer.flush();//强制将输出流缓冲区的数据送出
            } catch (Exception ex) {
                ex.printStackTrace();
            }

            outgoing.setText("");
            outgoing.requestFocus();
        }
    }

    public class IncomingReader implements Runnable {
        public void run() {//接收消息
            String message;
            try {

                while ((message = reader.readLine()) != null) {
                    System.out.println("Read " + message);
                    incoming.append(message + "\n");
                }
            } catch (Exception ex) {
                ex.getStackTrace();
            }
        }
    }
}

服务器

import java.io.*;
import java.net.*;
import java.util.*;

public class ChatServer {

    ArrayList clientOutputStreams;

    public class ClientHandler implements Runnable {
        BufferedReader reader;
        Socket sock;

        public ClientHandler(Socket clientSocket) {
            try {
                sock = clientSocket;
                InputStreamReader isReader = new InputStreamReader(sock.getInputStream());
                reader = new BufferedReader(isReader);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }

        public void run() {
            String message;
            try {
                while ((message = reader.readLine()) != null) {//发送消息
                    System.out.println("Read " + message);
                    tellEveryone(message);
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        new ChatServer().go();

    }

    public void go() {
        clientOutputStreams = new ArrayList();
        try {
            ServerSocket serverSock = new ServerSocket(4242);

            while (true) {//主线程接收消息,并将消息加入发送队列

                Socket clientSock =    serverSock.accept();
                PrintWriter writer = new PrintWriter(clientSock.getOutputStream());
                clientOutputStreams.add(writer);

                Thread t = new Thread(new ClientHandler(clientSock));
                t.start();
                System.out.println("Got a connection");
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public void tellEveryone(String message) {

        Iterator it = clientOutputStreams.iterator();//迭代器
        while (it.hasNext()) {
            try {
                PrintWriter writer = (PrintWriter) it.next();
                writer.println(message);
                writer.flush();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

}

Java序列化和文件的输入/输出

发表于 2017-08-14

序列化

将被序列化的对象写到文件中,可以通过解序列化重新读取这些对象,适用于Java环境,不能被其他程序引用。

将序列化程序写入文件

  1. 创建出FileOutputStream

    FileOutputStream fileStream = new FileOutputStream("MyGame.ser");
    

    这一步创建存取文件的 FileOutputStream对象,如果文件不存在会自动创建。

  2. 创建ObjectOutputStream

    ObjectOutputStream os = new ObjectOutputStream(fileStream);
    

    这一步将对象写入ObjectOutputStream,ObjectOutputStream无法直接连接文件,需要参数引导。

  3. 写入对象

    os.writeObject(CharaerOne);//将变量所引用的对象序列化并写入文件
    os.writeObject(CharaerTwo);//将变量所引用的对象序列化并写入文件
    os.writeObject(CharaerThree);//将变量所引用的对象序列化并写入文件
    
  4. 关闭ObjectOutputStream

    os.close();//关闭所关联的串流
    

数据在串流中移动

将串流(stream)连接起来代表来源与目的地的连接。串流必须连接到某处才能算是串流。
一般来说,串流要两两连接才能做出有意义的事情——其中一个表示连接,另一个则是要被调用方法的。以FileOutputStream为例,它有可以写入字节的方法。但我们通常不会直接写字节,而是以对象层次的观点来写入,所以需要高层的连接串流。

如果要让类序列化,就要实现Serializable接口

Serializable接口没有任何方法要实现,它的唯一目的就是声明有实现它的类是可以被序列化的。

序列化是全有或全无的

整个对象的主数据类型实例变量和引用到其他对象的实例变量都必须正确的序列化,不然就全部失败。

如果某实例变量不能或不应被实例化,就把它标记为transient(瞬时)的。

解序列化

  1. 创建FileInputStream

    FileInputStream fileStream = new FileInputStream("MyGame.ser");//若文件不存在则抛出异常
    

FileInputStream知道如何连接文件

  1. 创建ObjectInputStream

    ObjectInputStream os = new ObjectInputStream(fileStream);
    

ObjectInputStream知道如何读取对象,但是要靠链接的stream提供文件存取

  1. 读取对象

    Object one = os.readObject(); //每次调用readObject()都会从stream中读出下一个对象,读取顺序与读入顺序相同,次数超过会抛出异常
    Object two = os.readObject();
    Object three = os.readObject();
    
  2. 转换对象类型

    GameCharacter elf = (GameCharacte) one;
    GameCharacte troll = (GameCharacte) two;
    GameCharacte magician = (GameCharacte) three;
    
  3. 关闭ObjectInputStream

    os.close();//FileInputStream也会关掉
    

存储与恢复游戏人物

import java.io.*;

public class GameSaverTest {

    public static void main(String[] args) {
        GameCharacter one = new GameCharacter(50, "Elf", new String[] { "bow", "sword" });
        GameCharacter two = new GameCharacter(50, "Magic", new String[] { "cust", "sword" });
        GameCharacter three = new GameCharacter(50, "Knight", new String[] { "horse", "sword" });

        try {
            ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("Game.ser"));
            os.writeObject(one);
            os.writeObject(two);
            os.writeObject(three);
            os.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        }

        one = null;
        two = null;
        three = null;

        try {
            ObjectInputStream is = new ObjectInputStream(new FileInputStream("Game.ser"));
            GameCharacter oneRestore = (GameCharacter) is.readObject();
            GameCharacter twoRestore = (GameCharacter) is.readObject();
            GameCharacter threeRestore = (GameCharacter) is.readObject();

            System.out.println(oneRestore.getType());
            System.out.println(twoRestore.getType());
            System.out.println(threeRestore.getType());
        } catch (Exception ex) {
            ex.getStackTrace();
        }
    }

}

import java.io.*;

public class GameCharacter implements Serializable {
    int power;
    String type;
    String[] weapons;

    public GameCharacter(int p, String t, String[] w) {
        power = p;
        type = t;
        weapons = w;
    }

    public int getPower() {
        return power;
    }

    public String getType() {
        return type;
    }

    public String getWeapons() {
        String weaponList = "";

        for (int i = 0; i < weapons.length; i++) {
            weaponList += weapons[i] + " ";
        }

        return weaponList;
    }
}

序列化要点

  • 你可以通过序列化来存储对象的状态
  • 使用ObjectOutputStream来序列化对象(java.io)
  • Stream是连接串流或是链接用的串流
  • 连接串流用来表示源或目的地、文件、网络套接字连接
  • 链接用串流用来衔接连接串流
  • 用FileOutputStream链接ObjectOutputStream来将对象序列化到文件上
  • 调用ObjectOutputStream的writeObject(theObject)来将对象序列化,不需要调用FileOutputStream的方法
  • 对象必须实现序列化这个接口(Serializable)才能被序列化。如果父类实现序列化,则子类也就自动地实现,而不管是否有明确的声明
  • 当对象被序列化时,整个对象版图都会被序列化。这代表它的实例变量所引用的对象也会被实例化
  • 如果有不能被序列化的对象,执行期间就会抛出异常
  • 若实例变量被标记为transient。该变量在还原的时候会被赋予null或主数据类型的默认值
  • 在解序列化时,所有的类都必须能让Java虚拟机找到
  • 读取对象的顺序必须与写入的顺序相同
  • readObject()的返回类型是Object,因此解序列化回来的对象还需要转换成原来的类型
  • 静态变量不会被序列化,因为所有对象都是共享同一份静态变量值

文件输入/输出

将字符串写入文件

使用FileWriter将文本数据(字符串)写入文件

import java.io.*;

public class WriteAFile {

    public static void main(String[] args) {

        try {
            FileWriter writer = new FileWriter("Foo.txt");//如果没有就会创建

            writer.write("Hello Foo");

            writer.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

}

文本文件范例:e-Flashcard

使用三个类来写电子版flashcard:

  1. QuizCardBuilder,设计并存储卡片的工具
  2. QuizCardPlayer,加载并播放卡片的引擎
  3. QuizCard,表示卡片数据的类

QuizCard

public class QuizCard {
    private String question;
    private String answer;

    public QuizCard (String q, String a) {
        question = q;
        answer = a;
    }

    public String getQuestion() {
        return question;
    }

    public String getAnswer() {
        return answer;
    }
}

QuizCardBuilder

import java.util.*;
import java.awt.event.*;
import javax.swing.*;
import java.awt.*;
import java.io.*;

public class QuizCardBuilder {

    private JTextArea question;
    private JTextArea answer;
    private ArrayList<QuizCard> cardList;
    private JFrame frame;

    public static void main(String[] args) {
        QuizCardBuilder builder = new QuizCardBuilder();
        builder.go();
    }

    public void go() {

        frame = new JFrame("Quiz Card Builder");
        JPanel mainPanel = new JPanel();
        mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
        Font bigFont = new Font("sanserif", Font.BOLD, 24);
        question = new JTextArea(6,20);
        question.setLineWrap(true);//设置在行过长的时候是否要换行
        question.setWrapStyleWord(true);//设置在单词过长的时候是否要把长单词移到下一行
        question.setFont(bigFont);

        JScrollPane qScroller = new JScrollPane(question);
        qScroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
        qScroller.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);

        answer = new JTextArea(6,20);
        answer.setLineWrap(true);//设置在行过长的时候是否要换行
        answer.setWrapStyleWord(true);//设置在单词过长的时候是否要把长单词移到下一行
        answer.setFont(bigFont);

        JScrollPane aScroller = new JScrollPane(answer);
        aScroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
        aScroller.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);

        JButton nextButton = new JButton("Next Card");

        cardList = new ArrayList<QuizCard>();

        JLabel qLabel = new JLabel("Question:");
        qLabel.setFont(bigFont);
        JLabel aLabel = new JLabel("Answer:");
        aLabel.setFont(bigFont);

        mainPanel.add(qLabel);
        mainPanel.add(qScroller);
        mainPanel.add(aLabel);
        mainPanel.add(aScroller);
        mainPanel.add(nextButton);
        nextButton.addActionListener(new NextCardListener());

        JMenuBar menuBar = new JMenuBar();
        JMenu fileMenu = new JMenu("File");
        JMenuItem newMenuItem = new JMenuItem("New");
        JMenuItem saveMenuItem = new JMenuItem("Save");

        newMenuItem.addActionListener(new NewMenuListener());
        saveMenuItem.addActionListener(new SaveMenuListener());

        fileMenu.add(newMenuItem);
        fileMenu.add(saveMenuItem);
        menuBar.add(fileMenu);

        frame.setJMenuBar(menuBar);
        frame.getContentPane().add(BorderLayout.CENTER, mainPanel);
        frame.setSize(500, 600);
        frame.setVisible(true);        
    }

    public class NextCardListener implements ActionListener { 
        public void actionPerformed (ActionEvent ev) {
            QuizCard card = new QuizCard(question.getText(), answer.getText());
            cardList.add(card);
            clearCard();
        }
    }

    public class SaveMenuListener implements ActionListener {
        public void actionPerformed (ActionEvent ev) {
            QuizCard card = new QuizCard(question.getText(), answer.getText());
            cardList.add(card);

            JFileChooser fileSave = new JFileChooser();
            fileSave.showSaveDialog(frame);
            saveFile(fileSave.getSelectedFile());
        }
    }

    public class NewMenuListener implements ActionListener {
        public void actionPerformed(ActionEvent ev) {
            cardList.clear();
            clearCard();
        }
    }

    private void clearCard() {
        question.setText("");
        answer.setText("");
        question.requestFocus();
    }

    private void saveFile(File file) {
        try {
            BufferedWriter writer = new BufferedWriter(new FileWriter(file));

            for(QuizCard card : cardList) {
                writer.write(card.getQuestion() + "/");
                writer.write(card.getAnswer() + "\n");
            }
            writer.close();

        } catch (IOException ex) {
            System.out.println("couldn't write the cardlist out");
            ex.getStackTrace();
        }
    }
}

关于JmenuBar

JMenuBar

java.io.File.class

File这个类型代表磁盘上的文件,但并不是文件中的内容。

可以对File做的事情:

  1. 创建出代表现存盘文件的File对象。

    File f = new File("MyCode.txt");
    
  2. 建立新的目录。

    File dir = new File("Chapter");
    dir.mkdir();
    
  3. 列出目录下的内容。

    if (dir.isDirectory()) {
        String[] dirContents = dir.list();
        for (int i = 0; i < dirContents.length; i++) {
            System.out.println(dirContents[i]); 
        }
    }
    
  4. 取得文件绝对路径。

    System.out.println(dir.getAbsolutePath());
    
  5. 删除文件或目录(成功会返回true)

    bool isDeleted = f.delete();
    

读取文本文件

import java.io.*;

public class ReadFile {

    public static void main(String[] args) {

        try {
            File myFile = new File("MyText.txt");//找不到文件会报错
            FileReader fileReader = new FileReader(myFile);//FileReader是字符连接到文本文件的串流

            BufferedReader reader = new BufferedReader(fileReader); //将FileReader链接到BufferedReader以获得更高的效率。它只会在缓冲区读空的时候才会回头到磁盘读取

            String line = null; //用来存储获得的结果

            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            } 

            reader.close();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

文本文件范例:e-Flashcard

QuizCardPlayer

import java.util.*;
import java.awt.event.*;
import javax.swing.*;
import java.awt.*;
import java.io.*;

public class QuizCardPlayer {

    private JTextArea display;
    private ArrayList<QuizCard> cardList;
    private QuizCard currentCard;
    private int currentCardIndex;
    private JFrame frame;
    private JButton nextButton;
    private boolean isShowAnswer;

    public static void main(String[] args) {
        QuizCardPlayer reader = new QuizCardPlayer();
        reader.go();
    }

    public void go() {

        frame = new JFrame("Quiz Card Player");
        JPanel mainPanel = new JPanel();
        Font bigFont = new Font("sanserif", Font.BOLD, 24);

        display = new JTextArea(10,20);
        display.setFont(bigFont);

        display.setLineWrap(true);
        display.setEditable(false);

        JScrollPane qScroller = new JScrollPane(display);
        qScroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
        qScroller.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);

        nextButton = new JButton("Show Qusetion");

        mainPanel.add(qScroller);
        mainPanel.add(nextButton);
        nextButton.addActionListener(new NextCardListener());

        JMenuBar menuBar = new JMenuBar();
        JMenu fileMenu = new JMenu("File");
        JMenuItem loadMenuItem = new JMenuItem("Load card set");
        loadMenuItem.addActionListener(new OpenMenuListener());
        fileMenu.add(loadMenuItem);
        menuBar.add(fileMenu);

        frame.setJMenuBar(menuBar);
        frame.getContentPane().add(BorderLayout.CENTER, mainPanel);
        frame.setSize(640,500);
        frame.setVisible(true);
    }

    public class NextCardListener implements ActionListener {
        public void actionPerformed(ActionEvent ev) {
            if (isShowAnswer) {
                //显示答案
                display.setText(currentCard.getAnswer());
                nextButton.setText("Next Card");
                isShowAnswer = false;
            } else {
                //显示问题
                if (cardList == null){
                    System.out.println("first open a card");
                } else if (currentCardIndex < cardList.size()) {
                    showNextCard();
                } else {
                    display.setText("That was last card");
                    nextButton.setEnabled(false);
                }
            }
        }
    }

    public class OpenMenuListener implements ActionListener {
        public void actionPerformed(ActionEvent ev) {
            JFileChooser fileOpen = new JFileChooser();//打开文件的对话框让用户选择文件
            fileOpen.showOpenDialog(frame);
            loadFile(fileOpen.getSelectedFile());
        }
    }

    private void loadFile(File file) {
        cardList = new ArrayList<QuizCard>();
        try {
            BufferedReader reader = new BufferedReader(new FileReader(file));
            String line = null;
            while ((line = reader.readLine()) != null) {//读取一行
                makeCard(line);//做成QuizCard加入到cardList
            }
            reader.close();
        } catch (Exception ex) {
            System.out.println("couldn't read the card file");
            ex.printStackTrace();
        }
    }

    private void makeCard(String lineToParse) {
        String[] result = lineToParse.split("/");
        QuizCard card = new QuizCard(result[0], result[1]);
        cardList.add(card);
        System.out.println("made a card");
    }

    private void showNextCard() {
        currentCard = cardList.get(currentCardIndex);
        currentCardIndex++;
        display.setText(currentCard.getQuestion());
        nextButton.setText("Show Answer");
        isShowAnswer = true;
    }
}

Java.Swing

发表于 2017-08-12

创建GUI四个步骤回顾

  1. 创建window(JFrame)。

    JFrame frame = new JFrame();
    
  2. 创建组件。

    JButton button = new JButton();
    
  3. 把组件加到frame上。

    frame.getContentPane().add(BorderLayout.EAST, button);
    
  4. 显示出来。

    frame.setSize(300,300);
    frame.setVisible(true);
    

布局管理器(Layout Managers)

布局管理器是个与特定组件相关联的Java对象,它大多数是背景组件。布局管理器用来控制所关联组件上携带的其他组件。也就是说,如果某个框架带有面板,而面板带有按钮,则面板的布局管理器控制着按钮的大小与位置,而框架的布局管理器则控制着面板的大小与位置。
携带就是加入到上面:

myPanel.add(button);  

三个常用布局管理器

BorderLayout

BordLayout布局五个区域

import javax.swing.*;
import java.awt.*;

public class Button1 {

    public static void main(String[] args) {
        Button1 gui = new Button1();
        gui.go();
    }

    public void go() {
        JFrame frame = new JFrame();
        JButton button = new JButton("click me..............");
        Font bigFont = new Font("serif", Font.BOLD,28);//改字体
        button.setFont(bigFont);

        frame.getContentPane().add(BorderLayout.NORTH, button);

        frame.setSize(300,300);
        frame.setVisible(true);
    }

}

总的来说:南北可以控制高度,宽度一定;东西可以控制宽度,高度一定。中间只能捡剩下的。

FlowLayout布局组件的流向:依次从左至右,从上至下

JPanel的默认布局管理器是FlowLayout。

import javax.swing.*;
import java.awt.*;

public class Panel1 {

    public static void main(String[] args) {
        Panel1 gui = new Panel1();
        gui.go();
    }

    public void go() {
        JFrame frame = new JFrame();
        JPanel panel = new JPanel();
        panel.setBackground(Color.darkGray);

        JButton button = new JButton("shock me");
        JButton button2 = new JButton("sleep");
        JButton button3 = new JButton("go home");

        frame.getContentPane().add(BorderLayout.EAST, panel);
        panel.add(button);
        panel.add(button2);
        panel.add(button3);

        frame.setSize(300, 300);
        frame.setVisible(true);
    }

}

flow

BoxLayout布局:可以指定组件排列方向

import javax.swing.*;
import java.awt.*;

public class Panel1 {

    public static void main(String[] args) {
        Panel1 gui = new Panel1();
        gui.go();
    }

    public void go() {
        JFrame frame = new JFrame();
        JPanel panel = new JPanel();
        panel.setBackground(Color.darkGray);

        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));//构造函数要知道管理哪个组件,和用哪个轴

        JButton button = new JButton("shock me");
        JButton button2 = new JButton("sleep");
        JButton button3 = new JButton("go home");

        frame.getContentPane().add(BorderLayout.EAST, panel);
        panel.add(button);
        panel.add(button2);
        panel.add(button3);

        frame.setSize(300, 300);
        frame.setVisible(true);
    }

}

box

操作Swing组件

JTextField

public class Panel1 {

    public static void main(String[] args) {
        Panel1 gui = new Panel1();
        gui.go();
    }

    public void go() {
        JFrame frame = new JFrame();
        JPanel panel = new JPanel();
        JPanel panel2 = new JPanel();
        panel.setBackground(Color.darkGray);

        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));//构造函数要知道管理哪个组件,和用哪个轴

        JButton button = new JButton("shock me");
        JButton button2 = new JButton("sleep");
        JButton button3 = new JButton("go home");
        JTextField field = new JTextField(40);//构造JTextField,参数是字款
        JLabel label = new JLabel("your name:");

        frame.getContentPane().add(BorderLayout.NORTH, panel2);
        frame.getContentPane().add(BorderLayout.EAST, panel);
        panel.add(button);
        panel.add(button2);
        panel.add(button3);

        panel2.add(label);
        panel2.add(field);

        frame.setSize(300, 300);
        frame.setVisible(true);
    }

}

textf

method

JTextArea

JTextArea可以有超过一行以上的文字。如果要让JTextArea滚动,则要将它粘在ScrollPane上。

import javax.swing.*;
import java.awt.*;

public class Panel1 {

    public static void main(String[] args) {
        Panel1 gui = new Panel1();
        gui.go();
    }

    public void go() {
        JFrame frame = new JFrame();
        JPanel panel = new JPanel();
        JPanel panel2 = new JPanel();
        panel.setBackground(Color.darkGray);

        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));//构造函数要知道管理哪个组件,和用哪个轴

        JButton button = new JButton("shock me");
        JButton button2 = new JButton("sleep");
        JButton button3 = new JButton("go home");
        JLabel label = new JLabel("your name:");

        JTextArea text = new JTextArea(10, 20);//10行高,20字宽

        JScrollPane scroller = new JScrollPane(text);//把text赋给JScrollpane
        text.setLineWrap(true);//自动换行

        scroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);//只垂直滚动
        scroller.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);

        panel2.add(label);
        panel2.add(scroller);

        frame.getContentPane().add(BorderLayout.CENTER, panel2);
        frame.getContentPane().add(BorderLayout.EAST, panel);
        panel.add(button);
        panel.add(button2);
        panel.add(button3);




        frame.setSize(300, 300);
        frame.setVisible(true);
    }

}

一些常用方法

  1. 替换文字内容

    text.setText("remove");
    
  2. 加入文字

    text.append("append");
    
  3. 选取内容

    text.selectAll();
    
  4. 把GUI目前焦点拉回到文本字段以便让用户输入操作

    text.requestFocus();
    

JTextArea范例

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class TextArea1  implements ActionListener {

    JFrame frame;
    JTextArea text;
    public static void main(String[] args) {
        TextArea1 gui = new TextArea1();
        gui.go();
    }

    public void go() {
        frame = new JFrame();
        JPanel panel = new JPanel();
        text = new JTextArea(10,20);
        JScrollPane scroller = new JScrollPane(text);

        text.setLineWrap(true);//自动换行

        scroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);//只垂直滚动
        scroller.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);

        panel.add(scroller);
        JButton button = new JButton("append sth.");
        button.addActionListener(this);

        frame.getContentPane().add(BorderLayout.CENTER,panel);
        frame.getContentPane().add(BorderLayout.SOUTH, button);

        frame.setSize(400,400);
        frame.setVisible(true);
    }

    public void actionPerformed(ActionEvent ev) {
        text.append("button clicked.\n");
    }

}

JCheckBox

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class CheckBox1 implements ItemListener {//监听item的事件(是否选取)
    JCheckBox box;
    JTextArea text;

    public static void main(String[] args) {
        CheckBox1 gui = new CheckBox1();
        gui.go();
    }

    public void go () {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JPanel panel = new JPanel();
        text = new JTextArea(10,20);
        box = new JCheckBox("go home");//创建JCheckBox

        box.addItemListener(this);

        JScrollPane scroller = new JScrollPane(text);

        text.setLineWrap(true);//自动换行

        scroller.setVerticalScrollBarPolicy    、(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);//只垂直滚动
        scroller.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);

        panel.add(scroller);

        panel.add(box);
        frame.getContentPane().add(BorderLayout.CENTER, panel);

        frame.setSize(400,400);
        frame.setVisible(true);
    }

    public void itemStateChanged(ItemEvent ev) {
        if (box.isSelected()) {//是否勾选
            text.append("we go home!\n");
        } else {
            text.append("no no no.\n");
        }
    }
}

checkbox

JList

import javax.swing.*;
import javax.swing.event.*;//ListSelectionListener在这里!
import java.awt.*;

public class List1 implements ListSelectionListener {
    JTextField text;
    JList list;
    public static void main(String[] args) {
        List1 gui = new List1();
        gui.go();
    }

    public void go() {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JPanel panel = new JPanel();
        text = new JTextField(20);

        String [] listEntries = { "actor", "fireman", "police", "doctor", "lawer",
            "nurse", "player", "dog" };
        list = new JList(listEntries);
        list.setVisibleRowCount(4);//设定显示的行数
        list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);//只能选一个
        list.addListSelectionListener(this);//对选择事件注册

        JScrollPane scroller = new JScrollPane(list);
        scroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);//只垂直滚动
        scroller.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        panel.add(scroller);
        panel.add(text);

        frame.getContentPane().add(BorderLayout.CENTER, panel);
        frame.setSize(400, 400);
        frame.setVisible(true);
        }

    public void valueChanged(ListSelectionEvent lse) {
        if (!lse.getValueIsAdjusting()) {//没有这个会得到两次事件
            String selection = (String) list.getSelectedValue();//获得选择是个Object不一定是String
            text.setText(selection);
        }
    }

}

list

Java音乐小程序

发表于 2017-08-11

创建MIDI音乐播放器

构思

要完成这个程序,我们需要用到:

  • JavaSound API
  • 创建Swing GUI
  • 通过网络链接到其他计算机
  • 输入\输出数据

JavaSound API

MIDI

MIDI(Musical Instrument Digital Interface)乐器数字接口,也是不同电子发声装置沟通的标准协议。
MIDI数据表示执行的动作,但没有实际的声音,实际声音靠装置发出。

JavaSound的工作原理:

  1. 取得Sequencer并将它打开。
  2. 创建新的Sequence。
  3. 从Sequence中创建新的Track。
  4. 填入MidiEvent。
  5. 让Sequencer播放。
import javax.sound.midi.*;

public class MiniMiniMusicApp {

public static void main(String[] args) {
    MiniMiniMusicApp mini = new MiniMiniMusicApp();
        mini.play();
    }

    public void play() {

        try {
            //取得Sequencer并将它打开
            Sequencer player = MidiSystem.getSequencer();
            player.open();

            //创建新的Sequence
            Sequence seq = new Sequence(Sequence.PPQ, 4);

            //从Sequence中创建新的Track
            Track track = seq.createTrack();

            //填入MidiEvent
            ShortMessage a = new ShortMessage();//创建Message
            a.setMessage(144, 1, 44, 100);//置入指令(类型,频道,音符,音量)
            MidiEvent noteOn = new MidiEvent(a, 1);//在第一拍启动a
            track.add(noteOn);

            ShortMessage b = new ShortMessage();
            b.setMessage(128, 1, 44, 100);
            MidiEvent noteOff = new MidiEvent(b, 16);//在16拍停止
            track.add(noteOff);

            //将Sequence送入Sequencer
            player.setSequence(seq);

            //让Sequencer播放
            player.start();

        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

}

使用命令列表参数

import javax.sound.midi.*;

public class MiniMusicCmdLine {

    public static void main(String[] args) {
        MiniMusicCmdLine mini = new MiniMusicCmdLine();
        if (args.length < 2) {
            System.out.println("Don't forget the instrument and note args.");
        } else {
            int instrument = Integer.parseInt(args[0]);
            int note = Integer.parseInt(args[1]);
            mini.play(instrument, note);
        }
    }

    public void play(int instrument, int note) {
        try {
            //取得Sequencer并将它打开
            Sequencer player = MidiSystem.getSequencer();
            player.open();

            //创建新的Sequence
            Sequence seq = new Sequence(Sequence.PPQ, 4);

            //从Sequence中创建新的Track
            Track track = seq.createTrack();

            MidiEvent event =null;

            ShortMessage first = new ShortMessage();
            first.setMessage(192, 1, instrument, 0);
            MidiEvent changrInstrument = new MidiEvent(first, 1);
            track.add(changrInstrument);

            //填入MidiEvent
            ShortMessage a = new ShortMessage();
            a.setMessage(144, 1, note, 100);
            MidiEvent noteOn = new MidiEvent(a, 1);
            track.add(noteOn);

            ShortMessage b = new ShortMessage();
            b.setMessage(128, 1, note, 100);
            MidiEvent noteOff = new MidiEvent(b, 16);
            track.add(noteOff);

            //将Sequence送入Sequencer
            player.setSequence(seq);

            //让Sequencer播放
            player.start();

        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

}

在播放音乐的同时输出图形

需要实现的功能:

  • 制作一系列MIDI信息(事件)来播放任意钢琴音
  • 对事件注册一个监听者
  • 开始sequencer的播放操作
  • 每当监听者的事件处理程序被调用时,在面板上画出一个随机的方块并调用repaint

制作程序的三步:

  1. 简单的制作MIDI事件
  2. 注册并监听事件,但没有图形。从命令栏对应每一拍输出一个信息
  3. 在第二版上加上输出图形

制作一系列MIDI信息

import javax.sound.midi.*;

public class MiniMusicPlayer1 {

    public static void main(String[] args) {

        try {
            //创建并打开Sequencer
            Sequencer sequencer = MidiSystem.getSequencer();
            sequencer.open();

            //创建Sequence并track
            Sequence seq = new Sequence(Sequence.PPQ, 4);
            Track track = seq.createTrack();

            for (int i = 5; i < 61; i++) {
                track.add(makeEvent(144,1,i,100,i));
                track.add(makeEvent(128,1,i,100,i+2));
            }

            //开始播放
            sequencer.setSequence(seq);
            sequencer.setTempoInBPM(220);
            sequencer.start();
            } catch (Exception ex) { ex.printStackTrace(); }
    }

    public static MidiEvent makeEvent(int comd, int chan, int one, int two, int tick) {
        MidiEvent event = null;
        try {
            ShortMessage a = new ShortMessage();
            a.setMessage(comd, chan, one, two);
            event = new MidiEvent(a, tick);
        } catch (Exception e) { e.printStackTrace(); }
        return event;
        }
}

第二版:加入监听事件

import javax.sound.midi.*;

public class MiniMusicPlayer2 implements ControllerEventListener {

    public static void main(String[] args) {
        MiniMusicPlayer2 mini = new MiniMusicPlayer2();
        mini.go();
    }

    public void go() {
        try {
            //创建并打开Sequencer
            Sequencer sequencer = MidiSystem.getSequencer();
            sequencer.open();

            int[] eventsIWant = {127};
            sequencer.addControllerEventListener(this, eventsIWant);//注册事件,监听者和监听的事件数

            //创建Sequence并track
            Sequence seq = new Sequence(Sequence.PPQ, 4);
            Track track = seq.createTrack();

            for (int i = 5; i < 61; i++) {
                track.add(makeEvent(144,1,i,100,i));

                track.add(makeEvent(176,1,127,0,i));//176是ControllerEvent的编号,用来监听

                track.add(makeEvent(128,1,i,100,i+2));
            }
            sequencer.setSequence(seq);
            sequencer.setTempoInBPM(220);
            sequencer.start();
            } catch (Exception ex) { ex.printStackTrace(); }
    }

    public void controlChange(ShortMessage event) {
        System.out.println("la");
    }

    public static MidiEvent makeEvent(int comd, int chan, int one, int two, int tick) {
        MidiEvent event = null;
        try {
            ShortMessage a = new ShortMessage();
            a.setMessage(comd, chan, one, two);
            event = new MidiEvent(a, tick);
        } catch (Exception e) { e.printStackTrace(); }
        return event;
        }
}

第三版:输出图形

import javax.sound.midi.*;
import java.io.*;
import javax.swing.*;
import java.awt.*;

public class MiniMusicPlayer3 {
    static JFrame f = new JFrame("My frist music video");
    static MyDrawPanel ml;

    public static void main(String[] args) {
        MiniMusicPlayer3 mini = new MiniMusicPlayer3();
        mini.go();
    }

    public void setUpGui() {
        ml = new MyDrawPanel();
        f.setContentPane(ml);
        f.setBounds(30, 30, 300, 300);
        f.setVisible(true);
    }

    public void go() {
        setUpGui();

        try {
            //创建并打开Sequencer
            Sequencer sequencer = MidiSystem.getSequencer();
            sequencer.open();

            int[] eventsIWant = {127};
            sequencer.addControllerEventListener(ml, eventsIWant);//注册事件,监听者和监    听的事件数

            //创建Sequence并track
            Sequence seq = new Sequence(Sequence.PPQ, 4);
            Track track = seq.createTrack();

            for (int i = 5; i < 61; i++) {

                int r = (int)((Math.random() * 50) + 1);
                track.add(makeEvent(144,1,r,100,i));

                track.add(makeEvent(176,1,127,0,i));//176是ControllerEvent的编号,用来监听

                track.add(makeEvent(128,1,r,100,i+2));
            }
            sequencer.setSequence(seq);
            sequencer.setTempoInBPM(220);
            sequencer.start();
            } catch (Exception ex) { ex.printStackTrace();}
    }

    public static MidiEvent makeEvent(int comd, int chan, int one, int two, int tick) {
        MidiEvent event = null;
        try {
            ShortMessage a = new ShortMessage();
            a.setMessage(comd, chan, one, two);
            event = new MidiEvent(a, tick);
        } catch (Exception e) { e.printStackTrace(); }
        return event;
    }

    class MyDrawPanel extends JPanel implements ControllerEventListener {
        boolean msg = false;

        public void controlChange(ShortMessage event) {
            msg = true;
            repaint();
        }


        public void paintComponent(Graphics g) {//只能由系统调用
            if (msg) {
                Graphics2D g2d = (Graphics2D) g;

                int red = (int) (Math.random() * 255);
                int green = (int) (Math.random() * 255);
                int blue = (int) (Math.random() * 255);

                Color color = new Color(red, green ,blue);

                g2d.setColor(color);
                int ht = (int) ((Math.random() * 120) + 10);
                int width = (int) ((Math.random() * 120) + 10);

                int x = (int) ((Math.random() * 40) + 10);
                int y = (int) ((Math.random() * 40) + 10);

                g2d.fillRect(x, y, ht, width);

                msg = false;

            }
        }
    }
}

创建BeatBox

  • 创建出带有256个复选框的GUI。初始的时候这些复选框都是未勾选的,乐器的名字用到16个JLabel,还有4个按钮。
  • 对4个按钮进行注册ActionListener。不需要个别监听复选框,我们不需要动态的改变样式,只需等用户点击start时再来检查复选框并制作出MIDI的track。
  • 设定MIDI系统。之前已经做过,现在还要在此基础上进行循环播放,并调整播放速度。
  • 在用户按下start时,真正开始程序,程序一次一行的取得复选框的状态并制作MIDI信息。持续播放直到用户取消

程序代码

import java.awt.*;
import javax.swing.*;
import javax.sound.midi.*;
import java.util.*;  //使用ArrayList
import java.awt.event.*;

public class BeatBox {

    JPanel mainPanel;
    ArrayList<JCheckBox> checkboxList; //把CheckBox存在ArrayList中
    Sequencer sequencer;
    Sequence sequence;
    Track track;
    JFrame theFrame;

    String [] instrumentNames = { "Bass Drum", "Closed Hi-Hat", "Open Hi-Hat",
            "Acoustic Snare", "Crash Cymbal", "Hand Clap", "High Tom", "Hi Bongo",
            "Maracas", "Whistle", "Low Conga", "Cowbell", "Vibraslap", "Low-mid Tom",
            "High Agogo", "Open Hi Conga" }; //乐器名称
    int [] instruments = { 35,42,46,38,49,39,50,60,70,72,64,56,58,47,67,63 };

    public static void main(String[] args) {
        new BeatBox().buildGUI();
    }

    public void buildGUI() {
        theFrame = new JFrame("Cyber BeatBox");//框架
        theFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        BorderLayout layout = new BorderLayout();
        JPanel background = new JPanel(layout); //背景面板,设定成BoederLayout
        background.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); //设定面板上的空白边缘

        checkboxList = new ArrayList<JCheckBox>();
        Box buttonBox = new Box(BoxLayout.Y_AXIS);

        JButton start = new JButton("start");
        start.addActionListener(new MyStartListener());
        buttonBox.add(start);

        JButton stop = new JButton("stop");
        stop.addActionListener(new MyStopListener());
        buttonBox.add(stop);

        JButton upTempo = new JButton("Tempo Up");
        upTempo.addActionListener(new MyUpTempoListener());
        buttonBox.add(upTempo);

        JButton downTempo = new JButton("Tempo Down");
        downTempo.addActionListener(new MyDownTempoListener());
        buttonBox.add(downTempo);

        Box nameBox = new Box(BoxLayout.Y_AXIS);
        for (int i = 0; i < 16; i++) {
            nameBox.add(new Label(instrumentNames[i]));
        }

        background.add(BorderLayout.EAST, buttonBox);
        background.add(BorderLayout.WEST, nameBox);


        theFrame.getContentPane().add(background);

        GridLayout grid = new GridLayout(16, 16); //表格形式的布局(参数是行、列)
        grid.setVgap(10);//表格上下间距
        grid.setHgap(2);//表格左右间距
        mainPanel = new JPanel(grid);
        background.add(mainPanel);

        for (int i = 0; i < 256; i++) { //创建checkbox组,设定为未勾选并加入到ArrayList和    mainPanel
            JCheckBox c = new JCheckBox();
            c.setSelected(false);
            checkboxList.add(c);
            mainPanel.add(c);
        }

        setUpMidi();

        theFrame.setBounds(50,50,400,400);
        theFrame.pack();//Frame.pack()是JAVA语言的一个函数,这个函数的作用就是根据窗口里面的    布局及组件的preferedSize来确定frame的最佳大小。
        theFrame.setVisible(true);
    }

    public void setUpMidi() {
        try {
            sequencer = MidiSystem.getSequencer();
            sequencer.open();
            sequence = new Sequence(Sequence.PPQ,4);
            track = sequence.createTrack();
            sequencer.setTempoInBPM(120);
        } catch (Exception e) { e.printStackTrace(); }
    }

    public void buildTrackAndStart() {
        int[] trackList = null;
        //去掉旧的做一个新的
        sequence.deleteTrack(track);
        track = sequence.createTrack();

        for (int i = 0; i < 16; i++) {
            trackList = new int[16];

            int key = instruments[i];

            for (int j = 0; j < 16; j++) {
                JCheckBox jc = (JCheckBox) checkboxList.get(j + (16 * i));
                if (jc.isSelected()) {
                    trackList[j] = key;
                } else {
                    trackList[j] = 0;
                }
            }//关闭内循环

            makeTracks(trackList);
            track.add(makeEvent(176,1,127,0,16));
        }//关闭外部循环

        track.add(makeEvent(192,9,1,0,15));//确保第16拍有事件,否则不会重复播放
        try {
            sequencer.setSequence(sequence);
            sequencer.setLoopCount(Sequencer.LOOP_CONTINUOUSLY);//重复无限此次
            sequencer.start();
            sequencer.setTempoInBPM(120);
        } catch (Exception e) { e.printStackTrace(); }
    }

    public void makeTracks(int[] list) {

        for (int i = 0; i < 16; i++) {
            int key = list[i];

            if (key != 0) {
                track.add(makeEvent(144,9,key,100,i));
                track.add(makeEvent(128,9,key,100,i+1));
            }
        }
    }

    public MidiEvent makeEvent(int comd, int chan, int one, int two, int tick) {
        MidiEvent event = null;
        try {
            ShortMessage a = new ShortMessage();
            a.setMessage(comd, chan, one, two);
            event = new MidiEvent(a, tick);
        } catch (Exception e) { e.printStackTrace(); }
        return event;
    }

    public class MyStartListener implements ActionListener {
        public void actionPerformed(ActionEvent a) {
            buildTrackAndStart();
        }
    }

    public class MyStopListener implements ActionListener {
        public void actionPerformed(ActionEvent a) {
            sequencer.stop();
        }
    }

    public class MyUpTempoListener implements ActionListener {
        public void actionPerformed(ActionEvent a) {
            float tempoFactor = sequencer.getTempoFactor();
            sequencer.setTempoFactor((float) (tempoFactor * 1.03));
        }
    }

    public class MyDownTempoListener implements ActionListener {
        public void actionPerformed(ActionEvent a) {
            float tempoFactor = sequencer.getTempoFactor();
            sequencer.setTempoFactor((float) (tempoFactor * .97));
        }
    }

}

加入存取功能的BeatBox

import java.awt.*;
import javax.swing.*;
import javax.sound.midi.*;
import java.util.*;  //使用ArrayList
import java.awt.event.*;
import java.io.*;

public class BeatBox {

    JPanel mainPanel;
    ArrayList<JCheckBox> checkboxList; //把CheckBox存在ArrayList中
    Sequencer sequencer;
    Sequence sequence;
    Track track;
    JFrame theFrame;

    String [] instrumentNames = { "Bass Drum", "Closed Hi-Hat", "Open Hi-Hat",
            "Acoustic Snare", "Crash Cymbal", "Hand Clap", "High Tom", "Hi Bongo",
            "Maracas", "Whistle", "Low Conga", "Cowbell", "Vibraslap", "Low-mid Tom",
            "High Agogo", "Open Hi Conga" }; //乐器名称
    int [] instruments = { 35,42,46,38,49,39,50,60,70,72,64,56,58,47,67,63 };

    public static void main(String[] args) {
        new BeatBox().buildGUI();
    }

    public void buildGUI() {
        theFrame = new JFrame("Cyber BeatBox");//框架
        theFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        BorderLayout layout = new BorderLayout();
        JPanel background = new JPanel(layout); //背景面板,设定成BoederLayout
        background.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); //设定面板上的空白边缘

        checkboxList = new ArrayList<JCheckBox>();
        Box buttonBox = Box.createVerticalBox();//等号后面的等价替换 new Box(BoxLayout.Y_AXIS);

        JButton start = new JButton("start");
        start.addActionListener(new MyStartListener());
        buttonBox.add(start);

        JButton stop = new JButton("stop");
        stop.addActionListener(new MyStopListener());
        buttonBox.add(stop);

        JButton upTempo = new JButton("Tempo Up");
        upTempo.addActionListener(new MyUpTempoListener());
        buttonBox.add(upTempo);

        JButton downTempo = new JButton("Tempo Down");
        downTempo.addActionListener(new MyDownTempoListener());
        buttonBox.add(downTempo);

        JButton saveTrack = new JButton("Save Track");
        saveTrack.addActionListener(new MySaveTrackListener());
        buttonBox.add(saveTrack);

        JButton restore= new JButton("Restore");
        restore.addActionListener(new MyRestoreListener());
        buttonBox.add(restore);

        Box nameBox = new Box(BoxLayout.Y_AXIS);
        for (int i = 0; i < 16; i++) {
            nameBox.add(new Label(instrumentNames[i]));
        }

        background.add(BorderLayout.EAST, buttonBox);
        background.add(BorderLayout.WEST, nameBox);


        theFrame.getContentPane().add(background);

        GridLayout grid = new GridLayout(16, 16);//表格类布局
        grid.setVgap(10);//表格上下间距
        grid.setHgap(2);//表格左右间距
        mainPanel = new JPanel(grid);
        background.add(mainPanel);

        for (int i = 0; i < 256; i++) { //创建checkbox组,设定为未勾选并加入到ArrayList和mainPanel
            JCheckBox c = new JCheckBox();
            c.setSelected(false);
            checkboxList.add(c);
            mainPanel.add(c);
        }

        setUpMidi();

        theFrame.setBounds(50,50,400,400);
        theFrame.pack();//Frame.pack()是JAVA语言的一个函数,这个函数的作用就是根据窗口里面的布局及组件的preferedSize来确定frame的最佳大小。
        theFrame.setVisible(true);
    }

    public void setUpMidi() {
        try {
            sequencer = MidiSystem.getSequencer();
            sequencer.open();
            sequence = new Sequence(Sequence.PPQ,4);
            track = sequence.createTrack();
            sequencer.setTempoInBPM(120);
        } catch (Exception e) { e.printStackTrace(); }
    }

    public void buildTrackAndStart() {
        int[] trackList = null;
        //去掉旧的做一个新的
        sequence.deleteTrack(track);
        track = sequence.createTrack();

        for (int i = 0; i < 16; i++) {
            trackList = new int[16];

            int key = instruments[i];

            for (int j = 0; j < 16; j++) {
                JCheckBox jc = (JCheckBox) checkboxList.get(j + (16 * i));
                if (jc.isSelected()) {
                    trackList[j] = key;
                } else {
                    trackList[j] = 0;
                }
            }//关闭内循环

            makeTracks(trackList);
            track.add(makeEvent(176,1,127,0,16));
        }//关闭外部循环

        track.add(makeEvent(192,9,1,0,15));//确保第16拍有事件,否则不会重复播放
        try {
            sequencer.setSequence(sequence);
            sequencer.setLoopCount(Sequencer.LOOP_CONTINUOUSLY);//重复无限此次
            sequencer.start();
            sequencer.setTempoInBPM(120);
        } catch (Exception e) { e.printStackTrace(); }
    }

    public void makeTracks(int[] list) {

        for (int i = 0; i < 16; i++) {
            int key = list[i];

            if (key != 0) {
                track.add(makeEvent(144,9,key,100,i));
                track.add(makeEvent(128,9,key,100,i+1));
            }
        }
    }

    public MidiEvent makeEvent(int comd, int chan, int one, int two, int tick) {
        MidiEvent event = null;
        try {
            ShortMessage a = new ShortMessage();
            a.setMessage(comd, chan, one, two);
            event = new MidiEvent(a, tick);
        } catch (Exception e) { e.printStackTrace(); }
        return event;
    }

    public class MyStartListener implements ActionListener {
        public void actionPerformed(ActionEvent a) {
            buildTrackAndStart();
        }
    }

    public class MyStopListener implements ActionListener {
        public void actionPerformed(ActionEvent a) {
            sequencer.stop();
        }
    }

    public class MyUpTempoListener implements ActionListener {
        public void actionPerformed(ActionEvent a) {
            float tempoFactor = sequencer.getTempoFactor();
            sequencer.setTempoFactor((float) (tempoFactor * 1.03));
        }
    }

    public class MyDownTempoListener implements ActionListener {
        public void actionPerformed(ActionEvent a) {
            float tempoFactor = sequencer.getTempoFactor();
            sequencer.setTempoFactor((float) (tempoFactor * .97));
        }
    }

    public class MySaveTrackListener implements ActionListener {
        public void actionPerformed(ActionEvent a) {
            JFileChooser fileSave = new JFileChooser();
            fileSave.showSaveDialog(theFrame);
            saveFile(fileSave.getSelectedFile());
        }
    }

    private void saveFile(File file) {
        boolean[] checkboxState = new boolean[256];

        for (int i = 0; i < 256; i++) {
            JCheckBox check = (JCheckBox) checkboxList.get(i);
            if (check.isSelected()) {
                checkboxState[i] = true;
            }
        }

        try {
            FileOutputStream fileStream = new FileOutputStream(file);
            ObjectOutputStream os = new ObjectOutputStream(fileStream);
            os.writeObject(checkboxState);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public class MyRestoreListener implements ActionListener {
        public void actionPerformed (ActionEvent a) {
            JFileChooser fileOpen = new JFileChooser();
            fileOpen.showOpenDialog(fileOpen);
            loadFile(fileOpen.getSelectedFile());
        }
    }

    private void loadFile(File file) {
        boolean[] checkboxState = null;
        try {
            FileInputStream fileIn = new FileInputStream(file);
            ObjectInputStream is = new ObjectInputStream(fileIn);
            checkboxState = (boolean[]) is.readObject();
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        for (int i = 0; i < 256; i++) {
            JCheckBox check = (JCheckBox) checkboxList.get(i);
            if (checkboxState[i]) {
                check.setSelected(true);
            } else {
                check.setSelected(false);
            }
        }

        sequencer.stop();
        buildTrackAndStart();
    }


}
123
Xu Chen

Xu Chen

programming self-learner

29 日志
7 标签
© 2017 Xu Chen
由 Hexo 强力驱动
主题 - NexT.Pisces