
有两年Android开发经验,具备一定的Web基础,接触过matlab数字图像处理,ARM/ROS开发的基础知识
我的博客即将入驻“云栖社区”,诚邀技术同仁一同入驻。
呐,今天刚刚结束最后一门课的考试,放松是放松了那么一点吧。 下面这个是考完试回宿舍的路上拍的,感觉意境挺不错, 还有,这个。。昨晚复习完了突然有那么些感觉额,就写了写字,算是一般般呐大家看看就好哈哈哈 好了那话不多说,进入正题, 就,本学期学的课程有点多,也有点杂, 起初是觉得有些课程我们做个笔记是比较好了,一来可以整理整理知识和思路,二来复习起来也容易; 后来我想便干脆写成博文,跟大家一起分享也挺好,于是有了各门课的不少博文,现在在这里按照学科门类把本学期做的所有博文笔记都汇总一下吧。 ASP.NET RFID课程前置——SQL巩固练习 ASP.NET (Web) + C#算法 | 生成随机数字序列(随机数字+每个数字取随机不重复的位置和颜色) 用ASP.NET做一个简单的数据流动展示 ASP.NET | 从零到一实战分析对后台数据库增加数据、模糊查找、精确查找并展示 ROS机器人程序设计 机器人程序设计_ROS_note1 机器人程序设计_ROS_note2 VMware Workstation14.1.3 & Ubuntu18.04从安装到实用的填坑之路 ROS安装全过程(十分壮观,是个大坑来的) ROS_安装一个第三方仿真软件包——机器人模拟世界 基于VM14+ Ubuntu 16.04安装VMware Tools(VM同主机file交互的工具)以及使用的骚操作 ROS_机器人urdf建模仿真实践 ROS | 无法锁定管理目录(/var/lib/dpkg/),是否有其他进程正占用它 ROS机器人程序设计 | 期末知识点大总结 实用总线与接口技术(单片机编程) Keil uVision4起步简单编程 __note1 AT80C51串口通信编程 | 按键控制LED灯列 ARM ARM | STM32F10xxx课堂学习笔记(时钟 & 高级控制定时器) DSP DSP_代码笔记(基于TMS320X281x) DSP_代码笔记(基于TMS320X281x)| CPU定时器0模块 FPGA FPGA期末项目 | 数字时钟
戳这里下载整个项目包(已上传到CSDN资源库) 一、实验设备 FPGA开发平台、计算机、其它外接器件 二、需求分析(选题的意义、功能要求等。。。这里有点水,小伙伴们可以选择性跳过) 选题的意义:个人认为本项目(《数字时钟》)的选题意义有二,其一,时钟和闹钟早已是老生常谈的日常工具,利用课堂上所学习的知识贯通运用到现实生活中,作为操作实践,具有一定的现实意义;其二,数字时钟的功能设计囊括了数码管、LCD屏、开关运用、管教分配等知识,能够对本学期所学的实验知识做一个挽接,在知识的总结上也具备一定意义; 功能要求: 1.用数码管显示时、分、秒:分为两个界面,即时钟界面以及闹钟设置界面,显示时钟的时分秒以及闹钟的时分秒,可以通过开关切换显示,是项目的基本功能; 2.能按比例缩短时间调试:调控时钟或闹钟的频跳速度,方便演示和调试; 3.闹钟功能:用户可以通过sw8切换进入闹钟界面,再利用sw1-3设定具体的闹钟时间,到点即响,同样是项目的基本功能; 4.用LCD屏显示日期(年月日)以及祝福语:作为时钟,显示年月日的功能个人觉得也是有必要的,另外关于祝福语,我们对实验和知识的学习其实本身就是快乐的过程,生活也没有必要每天都过得毫无色彩、千篇一律,所以怀揣着这份情怀呢,我在本项目中加了一个显示祝福语的功能,意在表达自己的这份对科学和生活的热爱以及学习的热情。 三、系统框架介绍(系统结构图,各模块的功能及端口介绍,等) 系统结构图如下所示: 系统结构图 各模块的功能及端口介绍: (这里的模块功能和端口做简要的介绍,详细的用法请移步至《设计思路》部分) 1.Digital_CLK_Top(clk_50M, Reset_n, seg7,ledcom,key,beep, LCD_ON1,LCD_BLON1,LCD_RS1,LCD_RW1,LCD_EN1,LCD_DATA1); 功能:顶层模块,调用其他子模块,统筹整个系统的功能; 端口: clk_50M, //系统时钟输入 Reset_n, //系统复位输入 seg7, //数码管显示输出位 ledcom, //数码管位置调控输出位 key, //开关输入号位,主要使用于CLKcounter_60BCD beep, //蜂鸣器输出信号 LCD_ON1, //LCD供电电源开关 LCD_BLON1, //LCD背景电源开关 LCD_RS1, //寄存器选择信号 LCD_RW1, //液晶读写信号 LCD_EN1, //液晶时钟信号 LCD_DATA1, //LCD的数据端口 2.lcd_ori(LCD_ON,LCD_BLON,LCD_RS,LCD_RW,LCD_EN,LCD_DATA,CLK); 功能:LCD屏显示控制模块,用于控制LCD屏显示年月日以及祝福语; 端口: LCD_ON, //LCD供电电源开关 LCD_BLON, //LCD背景电源开关 LCD_RS, //寄存器选择信号 LCD_RW, //液晶读写信号 LCD_EN, //液晶时钟信号 LCD_DATA, //LCD的数据端口 CLK, //模块时钟输入(项目中输入的是clk_50M) 3.seg_display(clk,rst_n,mimute_cnt,second_cnt,hour_cnt,ledcom,seg7); 功能:基于视觉暂留知识,控制时钟界面数码管的显示; 端口: clk, //模块时钟输入 rst_n, //复位信号输入 mimute_cnt, //分钟位数据输入 second_cnt, //秒位数据输入 hour_cnt, //时位数据输入 seg7, //数码管显示输出位 ledcom, //数码管位置调控输出位 4.counter_60BCD(clk, rst, couter_o,flag); 功能:在顶层模块调用,根据输入的时钟和时|分|秒位数据进行对应的加一计算和进位计算,并在溢出(分秒59加一溢出,时23加一溢出)时返回一个flag供顶层模块使用; 端口: clk, //模块时钟输入 rst, //复位信号输入 couter_o, //数字时钟部分的时|分|秒位数据输出 flag, //输出一个溢出进位标志 5.clk_gen(clk_in,rst,clk_out); //clk_in=50MHz 功能:对输入的50MHz的系统时钟分频,调用时根据CLK_DIV变量的复用情况进行运算并返回对应的时钟分频结果; 端口: clk_in, //模块时钟输入,在顶层模块 rst, //复位信号输入 clk_out, //时钟分频结果输出 6.CLKseg_display(clk,rst_n,mimute_cnt,second_cnt, hour_cnt,CLKmimute_cnt,CLKsecond_cnt,CLKhour_cnt,ledcom,seg7,sw8); 功能:基于视觉暂留知识,控制闹钟设置界面数码管的显示,并基于sw8的状态控制切换数码管屏显示; 端口: clk, //模块时钟输入 rst, //复位信号输入 mimute_cnt, //分钟位数据输入(时钟) second_cnt, //秒位数据输入(时钟) hour_cnt, //时位数据输入(时钟) CLKmimute_cnt,//分钟位数据输入(闹钟设置) CLKsecond_cnt,//秒位数据输入(闹钟设置) CLKhour_cnt, //时位数据输入(闹钟设置) seg7, //数码管显示输出位 ledcom, //数码管位置调控输出位 sw8 //开关8的输入信号 7.CLKcounter_60BCD(clk, rst, couter_o,flag,sw); 功能:在顶层模块调用,根据输入的时钟频率clk和时|分|秒位数据进行对应的加一计算和进位计算,并在溢出(分秒59加一溢出,时23加一溢出)时返回一个flag供顶层模块使用; 端口: clk, //模块时钟输入 rst, //复位信号输入 couter_o, //数字闹钟设置部分的时|分|秒位数据输出 flag, //输出一个溢出进位标志 sw //开关输入位,作为key的接收变量 四、设计思路(每个模块的设计思路,文字结合示意图等进行介绍) 1.Digital_CLK_Top(clk_50M, Reset_n, seg7,ledcom,key,beep, LCD_ON1,LCD_BLON1,LCD_RS1,LCD_RW1,LCD_EN1,LCD_DATA1); 功能:顶层模块,调用其他子模块,统筹整个系统的功能; 思路: A.复用两次分频模块clk_gen,产生两个速度不一样的时钟频——clk_second和clk_second2,分别用来驱动数字时钟和数字闹钟设置; B.复用三次时钟控制模块counter_60BCD: 第一次,用clk_second作为时钟频输入,传入second_cnt给couter_o作为秒位数据承载,MODULEofCNT设置为60,模块每溢出一次(即每计数到60个秒),产生一个flag,即flag_min; 第二次,用flag_min作为时钟频输入,传入minute_cnt给couter_o作为秒位数据承载,MODULEofCNT设置为60,模块每溢出一次(即每计数到60个分),产生一个flag,即flag_hour; 第三次,用flag_hour作为时钟频输入,传入hour_cnt给couter_o作为秒位数据承载,MODULEofCNT设置为24,模块每计数到24个时溢出一次; C.复用三次闹钟控制模块CLKcounter_60BCD: 第一次,用clk_second2作为时钟频输入,传入CLKsecond_cnt给couter_o作为秒位数据承载,MODULEofCNT设置为60,sw位传入key[2]信号,即sw3的状态信号; 第二次,用clk_second2作为时钟频输入,传入CLKminute_cnt给couter_o作为秒位数据承载,MODULEofCNT设置为60,sw位传入key[1]信号,即sw2的状态信号; 第三次, 用clk_second2作为时钟频输入,传入CLKhour_cnt给couter_o作为秒位数据承载,MODULEofCNT设置为24,sw位传入key[0]信号,即sw1的状态信号; D.复用LCD屏控制显示模块lcd_ori,根据本函数定义的变量复用对应的参数; E.复用闹钟数码管控制显示模块CLKseg_display,根据本函数定义的变量复用对应的参数; F.接下来,编写了一个状态机,主要设置并使用了state0、state1、state2等三个状态; state0状态:使用if判断语句 if(second_cnt==CLKsecond_cnt&&minute_cnt==CLKminute_cnt&&hour_cnt==CLKhour_cnt) 等一个在闹钟界面模块设置的时分秒数列,等到这个数列的时候转跳到state1或state2,并设置好蜂鸣器鸣响的延时时间到变量cnt_2; state1、state2:在计数变量cnt_2归零之前,一直给蜂鸣器管脚输出高电平,直到计数变量cnt_2归零,输出为低电平,停止蜂鸣器鸣响; 2.lcd_ori(LCD_ON,LCD_BLON,LCD_RS,LCD_RW,LCD_EN,LCD_DATA,CLK); 功能:LCD屏显示控制模块,用于控制LCD屏显示年月日以及祝福语; 思路: A.将年月日以及祝福语分别编写成字符串,再分别付给变量lcd_buf_first、data_first和lcd_buf_second、data_second; B.准备好状态参数常量—— clear_lcd、 set_disp_mode、 disp_on、 shift_down、 write_data_first、 write_data_second; 基于状态机思想,并用current_state变量承载状态常量; C.初始化LCD模块; D.clear_lcd状态, 清屏并光标复位 ; set_disp_mode: 设置显示模式:8位2行5x8点阵; disp_on: 显示器开、光标不显示、光标不允许闪烁 ; shift_down: 文字不动,光标自动右移;data_first赋值给lcd_buf_first; write_data_first、write_data_second: 用于写入数据 default: 若current_state为其他值,则将current_state置为clear_lcd; 3.seg_display(clk,rst_n,mimute_cnt,second_cnt,hour_cnt,ledcom,seg7); 功能:基于视觉暂留知识,控制时钟界面数码管的显示; 思路: A.复用分频模块clk_gen分频出一个时钟clk_div,周期约为0.1s; B.设置一个8位的变量cnt,基于clk_div进行递加一,并在0到满位溢出之间循环(满位溢出时将之归零,再继续加一处理); C.基于快速递加一的变量cnt,在每个clk时钟上升沿来的是时候,取其低三位(八个数码管格位,刚好三位二进制数可以完整表示)进行case处理, 每个case的子状态中,根据cnt低三位的值,把对应的表示数码管位置的二进制数赋值给ledcom,用于选择数码管格位;再把对应的数据(如秒位数据的低四位second_cnt[3:0],高四位second_cnt[7:4]等)传给dis变量; dis的用法是在下一个always模块里面,case判断dis的值,根据dis的值把对应的二进制数传给seg7变量,用于基于ledcom选择数码管格位之后,显示格位里面的内容; 由此,基于快速跳变的clk时钟和clk_div时钟,ledcom的值也不断地快速变换,由此数码管的每个格位都在被快速地选择和显示,于是这样便是通过了视觉暂留效应,实现了数码管时钟的显示; 4.counter_60BCD(clk, rst, couter_o,flag); 功能:在顶层模块调用,根据输入的时钟和时|分|秒位数据进行对应的加一计算和进位计算,并在溢出(分秒59加一溢出,时23加一溢出)时返回一个flag供顶层模块使用; 思路: A.将MODULEofCNT(分别通过/10和%10运算)切成5/2和9/3两个数,并分别付给变量a和b; B.通过a和b判断是对分秒位数据(MODULEofCNT为60)还是对时位数据(MODULEofCNT为24)进行计算; 若是分秒位数据:先对couter_o[7:4]进行判断,若小于5,则对couter_o[3:0]判断,若couter_o[3:0]小于9,则couter_o[3:0]数据加一,若couter_o[3:0]不小于于9,则couter_o[3:0]归零处理,couter_o[7:4]加一;若couter_o[7:4]不小于5,则对couter_o[3:0]判断,若couter_o[3:0]小于a(9),则couter_o[3:0]数据加一,若couter_o[3:0]不小于于a(9),则couter_o八个位都归零,并返回一个溢出进位flag(flag<=1); 若是时位数据:逻辑同上,只将b替换为2,将a替换为3进行处理即可; 5.clk_gen(clk_in,rst,clk_out); //clk_in=50MHz 功能:对输入的50MHz的系统时钟分频,调用时根据CLK_DIV变量的复用情况进行运算并返回对应的时钟分频结果; 思路:简单地基于系统时钟clk_in以及可复用的参数CLK_DIV实现分频的功能; 6.CLKseg_display(clk,rst_n,mimute_cnt,second_cnt, hour_cnt,CLKmimute_cnt,CLKsecond_cnt,CLKhour_cnt,ledcom,seg7,sw8); 功能:基于视觉暂留知识,控制闹钟设置界面数码管的显示,并基于sw8的状态控制切换数码管屏显示; 思路: A.复用分频模块clk_gen分频出一个时钟clk_div,周期约为0.1s; B.(基于sw8的状态控制切换数码管屏显示)If语句判断sw8时候为高电平,如果是,则将CLKsecond_cnt、CLKmimute_cnt、CLKhour_cnt系列数据付给dis变量,如果不是则将second_cnt系统数据付给dis变量;细节处如cnt、ledcom、dis、case(cnt[2:0])等用法则跟seg_display模块的设计思路相同; 7.CLKcounter_60BCD(clk, rst, couter_o,flag,sw); 功能:在顶层模块调用,根据输入的时钟频率clk和时|分|秒位数据进行对应的加一计算和进位计算,并在溢出(分秒59加一溢出,时23加一溢出)时返回一个flag供顶层模块使用; 思路:同counter_60BCD模块的设计思路; 五、实验结果 1.开机状态,初始为数字时钟界面,下图为数码管上规则地显示闪动的时钟,通过分频模块的参数调改可以改变其速度; LCD屏上显示具体的日期(年月日)以及祝福语(Wish you happy/happiness——祝你幸福),开关状态为全数低电平: 2.上推sw8,令之输出为高电平,数码管即切换到闹钟设置界面: 3.上推sw1,令之输出为高电平,数码管闹钟界面上“时”数位持续加一(图中已加到06): 4.上推sw2,令之输出为高电平,数码管闹钟界面上“分”数位持续加一(图中已加到04): 5.上推sw3,令之输出为高电平,数码管闹钟界面上“秒”数位持续加一(图中已加到05): 6.数码管停留在设置闹钟界面的时候,数字时钟一直也在后台跑动,当我们设置完闹钟,将开关全数归为低电平时,数码管界面回切到数字时钟界面,数字时钟上的时间到达闹钟设计的点时,会启动蜂鸣器,产生一个时长为三秒(可以在程序中设置时长)的鸣响作为闹钟。 六、实验项目代码清单 1.Digital_CLK_Top.v 顶层文件,用于调用诸多要使用的模块; 2.Tcl_2c35_script1.tcl 管教分配的脚本文件,描述管教的分配; 3.lcd_ori.v LCD屏模块,实现LCD屏显示年月日以及祝福语的功能; 4.seg_ex.v 5.seg_display.v 时钟模块的数码管显示的相关模块文件; 6.counter_60BCD.v 关于实现时钟界面显示的后台数字计算(相关变量的加一和进位操作等)的文件; 7.clk_gen.v 时钟分频模块,用于实现各种参数频率的分频; 8.CLKseg_display.v 闹钟模块的数码管管显示的相关模块文件; 9.CLKcounter_60BCD.v 关于实现闹钟界面显示的后台数字计算(相关变量的加一和进位操作等)的文件。
新建: 新建数据库mydb.mdf、添加表: 更新数据库; 右击数据库,选择属性,复制连接字符串;(这里之前做过了,就简单点过不做细节。点击这里可以看详细步骤截图参考_《用ASP.NET做一个简单的数据流动展示》) 到web.config: 注意相对路径|DataDirectory|...... <connectionStrings> <add name="DefaultConnection" connectionString="Data Source=(LocalDb)\MSSQLLocalDB;Initial Catalog=aspnet-manages-c703b6e8-d35a-4082-aadf-60f5ad784980;AttachDbFilename=|DataDirectory|\aspnet-manages-c703b6e8-d35a-4082-aadf-60f5ad784980.mdf;Integrated Security=SSPI" providerName="System.Data.SqlClient"/> <add name ="connstr" connectionString="Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename=|DataDirectory|\mydb.mdf;Integrated Security=True"/> </connectionStrings> 删除,重建default窗口文件; 设计,插入表: 设计: 顶行合并; 输入文字; 顶行内容居中; 二列添加textbox; 改id: Name_TextBox Cate_TextBox Price_TextBox Time_TextBox Address_TextBox Contact_TextBox 合并末行,加个button,修改text,加个GridView: 新建一个common类: 右击App_Code: using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Data; using System.Data.SqlClient; using System.Configuration; /// <summary> /// common 的摘要说明 /// </summary> public class common { public common() { // // TODO: 在此处添加构造函数逻辑 // } public static SqlConnection myconn() { string connstr = ConfigurationManager.ConnectionStrings["connstr"].ToString(); SqlConnection myconn = new SqlConnection(connstr); return myconn; } } 回来default窗口,双击设计界面里边入库按钮,开始编写逻辑: using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using System.Configuration; using System.Data; using System.Data.SqlClient; using System.Drawing; public partial class _Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) displayDB(); } protected void Button1_Click(object sender, EventArgs e) { insertDB(); displayDB(); } //套路:1.connstr-sqlconn-open打开数据库 //2.cmdstr-sqlcmd(cmdstr,conn)-cmd.ex执行数据库操作,或者其他; //3.关闭 protected void insertDB() { SqlConnection myconn = common.myconn(); myconn.Open(); double Pricevalue = Convert.ToDouble(Price_TextBox.Text.Trim()); string cmdstr = @"insert into Tproduct(Fname,Fcategory,Fprice,Ftime,Faddress,Fcontactname) values('" + Name_TextBox.Text + "' , '" + Cate_TextBox.Text + "' , " + Pricevalue + " , '" + Time_TextBox.Text + "' , '" + Address_TextBox.Text + "' , '" + Contact_TextBox.Text + "' )"; SqlCommand mycmd = new SqlCommand(cmdstr,myconn); mycmd.ExecuteNonQuery(); mycmd.Dispose(); myconn.Close(); Response.Write("<script>alert('入库成功')</script>"); } protected void displayDB() { string connstr = ConfigurationManager.ConnectionStrings["connstr"].ToString(); SqlConnection myconn = new SqlConnection(connstr); myconn.Open(); string cmdstr = @"select * from Tproduct"; SqlDataAdapter myda = new SqlDataAdapter(cmdstr,myconn);//查出 DataSet myds = new DataSet();//转型 myda.Fill(myds); GridView1.DataSource = myds;//赋能控件 GridView1.DataKeyNames = new string[] { "id" }; GridView1.DataBind(); myda.Dispose(); myds.Dispose(); myconn.Close(); } } 如图,插入和展示就完成了: 现在增加查找功能: 末行配置多三个控件: 分别添加点击逻辑即可: protected void jqFind_Button_Click(object sender, EventArgs e) { SqlConnection myconn = common.myconn(); myconn.Open(); string cmdstr = "select *from Tproduct where Fname='" + Find_TextBox.Text.Trim() + "'";//单引号双引号括起来 SqlDataAdapter myda = new SqlDataAdapter(cmdstr, myconn); DataSet myds = new DataSet(); myda.Fill(myds); int rowNum = myds.Tables[0].Rows.Count; int columnNum = myds.Tables[0].Columns.Count; DataTable usingTable = myds.Tables[0]; if (rowNum == 0) { usingTable = usingTable.Clone();//克隆 usingTable.Rows.Add(usingTable.NewRow());//加新行 GridView1.DataSource = usingTable;//赋能 GridView1.Rows[0].Cells.Clear();//清空 GridView1.Rows[0].Cells.Add(new TableCell());//加新格 GridView1.Rows[0].Cells[0].Text = "无相关记录";//text GridView1.Rows[0].Cells[0].ColumnSpan = columnNum; } else { GridView1.DataSource = myds; GridView1.DataBind(); } myds.Dispose(); myda.Dispose(); myconn.Close(); } protected void mhFind_Button_Click(object sender, EventArgs e) { SqlConnection myconn = common.myconn(); myconn.Open(); string cmdstr = "select *from Tproduct where Fname like '%"+Find_TextBox.Text.Trim()+"%'"; SqlDataAdapter myda = new SqlDataAdapter(cmdstr, myconn); DataSet myds = new DataSet(); myda.Fill(myds); GridView1.DataSource = myds; GridView1.DataBind(); myds.Dispose(); myda.Dispose(); myconn.Close(); } 效果: 精确查找 模糊查找
参考链接 本文摘要 flutter SDK的安装 在vscode中安装flutter和dart插件 vscode中如何安装和启动虚拟机 vscode中新建flutter项目并运行 flutter SDK的安装 进入flutter官网,下载对应的版本: 下载完: 配置环境变量 解压, 进入解压好的文件夹中的bin目录,点击地址栏,复制路径,待会儿用于配置环境变量: 到桌面--右键“此电脑”--点击属性--高级系统设置--环境变量--系统变量栏--点击Path,新建一个环境变量,把刚刚复制的路径加进来--确定--确定--确定: 确定之后便配置完成,接下来进行测试: Windows+R--输入cmd进入命令行窗口--窗口输入 flutter 之后回车--如果能看到如下诸多输出信息则表明配置环境变量成功也表明我们的flutter安装好了: cmd命令行继续调试: flutter doctor 该命令用于检测flutter环境的相关搭建情况, 总共有红色、绿色、黄色三种输出信息; Android Studio 安装插件(这里仅做步骤参考,实际应该用AS3.0+进行操作) 安装好了,点击 安装完成: 这里pick一下一个解决签证的方法: 在vscode中安装flutter和dart插件 打开VSCode,如下操作: 点击flutter的install按钮之后会出现如下界面,dart插件也会同时安装好,此时点击reload, 可以看到插件已经安装好了: 接下来点击左侧栏的第一个图标,进入文件界面,开始新建项目: 在vscode的终端中直接输入 flutter create [项目|文件夹名(这里用的是demo001)] 回车: 如图,得到新建的项目: 进入demo001/lib/main.dart: 有时候刚刚新建的项目可能会像下面这些图片中的代码所示各种爆红报错: 把鼠标移到首行的import处,还会显示如下报错:[dart] Target of URI doesn't exist: 'package:flutter/material.dart'. [uri_does_not_exist] 对于这种情况其实很好解决,重启VSCode即可。 点击下图箭头指向处: 会弹出在AS创建过的虚拟机(也建议在AS中创建虚拟机,而不是在VSCode这里创建虚拟机,因为这里新建虚拟机是不会有选项的,而AS不一样,有诸多选项可选): 现在点击上图的第一个虚拟机,启动之: 运行程序: 我觉得终究啊,还是要用3.0以上的gradle才能正正经经地把flutter给跑起来。 无论是AS还是VSCode。
需求:连接数据库,在网页上显示一行数据,总共十列,每两秒刷新一次,刷新时数据往前流动(后一个单元格覆盖前一个单元格,最后一个单元格生成一个随机数) 新建项目: 删除: 重建: 放入工具: 设置居中: 新建数据库: 新建表: 建表之后更新数据库: 更新好了之后点击刷新: 刷新后: 右键randomT,在菜单里选择显示表数据,进入如下窗口: 手动键入第一行数据,再点击第二行任一格,完成第一行数据的添加: 右键mydb.mdf: 点击属性,查看并复制连接字符串: 到web.config中使用: 改成相对路径: 编写Default.aspx.cs: using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using System.Configuration; using System.Data; using System.Data.SqlClient; using System.Drawing; public partial class _Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { showdata(); } protected void showdata() { string connstr = ConfigurationManager.ConnectionStrings["connstr"].ToString();//取出字符串 SqlConnection myconn = new SqlConnection(connstr); //字符串对象化 myconn.Open();//打开数据库 string cmdstr = "select * from randomT"; SqlDataAdapter myda = new SqlDataAdapter(cmdstr, myconn);//sql字符串对数据库处理 DataSet myds = new DataSet(); myda.Fill(myds);//处理完的数据fill到myds GridView1.DataSource = myds; GridView1.DataBind(); myds.Dispose(); myda.Dispose(); myconn.Close(); } } 到此,运行,显示数据: 设置属性: 双击生成事件: 最终编写Default.aspx.cs: using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using System.Configuration; using System.Data; using System.Data.SqlClient; using System.Drawing; public partial class _Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { } protected void showdata() { string connstr = ConfigurationManager.ConnectionStrings["connstr"].ToString();//取出字符串 SqlConnection myconn = new SqlConnection(connstr); //字符串对象化 myconn.Open();//打开数据库 string cmdstr = "select * from randomT"; SqlDataAdapter myda = new SqlDataAdapter(cmdstr, myconn);//sql字符串对数据库处理 DataSet myds = new DataSet(); myda.Fill(myds);//处理完的数据fill到myds SqlCommand mycmd = new SqlCommand(cmdstr,myconn); SqlDataReader mydr = mycmd.ExecuteReader();//读一整行的数据,注意在使用另外一个Execute的时候要将此关掉,不然会报错 int fieldCount = mydr.FieldCount;//得到列数 int[] valueArray = new int[fieldCount];//用来存列值 string[] fieldNames = new string[fieldCount];//用来存列名 for (int i = 0; i < fieldCount; i++)//获取列名 { fieldNames[i] = mydr.GetName(i).ToString(); } for (int i = 0; i < fieldCount; i++)//获取列值 { valueArray[i] = Convert.ToInt32(myds.Tables[0].Rows[0][i].ToString()); } mydr.Close();//解放用不到的资源,避免报错 for (int i = 1; i < fieldCount - 1; i++)//从后往前覆盖 { valueArray[i] = valueArray[i + 1]; } Random rd = new Random(); valueArray[fieldCount - 1] = rd.Next(0, 101); //更新数据库 for(int i = 1; i <= fieldCount - 1; i++) { string updateStr = @"update randomT set " + fieldNames[i] + " = " + valueArray[i] + "where id=1"; mycmd.CommandText = updateStr; mycmd.ExecuteNonQuery(); } GridView1.DataSource = myds; GridView1.DataBind(); myds.Dispose(); myda.Dispose(); myconn.Close(); } protected void Timer1_Tick(object sender, EventArgs e) { showdata(); } } 实现每两秒刷新数据:
工作空间的框架是怎么样的?有几个文件夹? 一个包含功能包、可编辑源文件或编译包的文件夹。 同时编译不同的功能包时非常有用,并且可以用来保存本地开发包。 编译命令是什么? 用catkin编译功能包 $ cmake package/ #使用标准Cmake工作流程,一次编译一个包 $ make $ cd workspace # 编译工作空间内的所有包 $ catkin_make Source是干什么?为什么要source? 用来刷新工作环境; 如果不刷新,系统可能无法识别编译后(如Catkin_make之后)的工作环境(里面的新文件); 什么是功能包? 功能包是ROS软件中的基本单元,包含ROS节点、库、配置文件。 一个功能包文件结构 这些文件夹的主要功能如下: config:放置功能包中的配置文件,由用户创建,文件名可以不同。 include:放置功能包中需要用到的文件。 scripts:放置可以直接运行的Python脚本。 src:程序源文件。 launch:放置功能包自定义的消息类型。 msg:放置功能包自定义的服务类型。 srv:放置功能包的定义的服务类型。 action:放置功能包自定义的动作指令。 CMakeLists.txt:Cmake的生成文件,编译器编译功能包的规则。 package.xml:功能包清单。 功能包里至少会包含什么文件? 新建一个工作空间 新建一个功能包 (刚刚新建的功能包,包含以下几个东西) 一个include文件夹——里面包含着一个与包名同名的空文件夹; 一个空src文件夹 一个CMakeLists.txt文件 一个package.xml文件 build目录下是保存什么的? 编译空间(build文件夹):CMake和catkin为功能包和项目保存缓存信息、配置和其他中间文件 devel目录下是保存什么的? 开发空间(devel文件夹):保存编译后的程序(无需安装就能用来测试的程序) ros操作系统的通讯方式有几种?分别是什么? ros操作系统的通讯方式有三种 话题通讯机制 服务通讯机制 参数通讯机制 拓展: 自定义通信格式有三种: 消息( msg) 服务(srv) 动作(action) 解释一下launch的作用? 作用:运行启动文件 在ROS中一个节点程序一般只能完成功能单一的任务,但是一个完整的ROS机器人一般由很多个节点程序同 时运行、相互协作才能完成复杂的任务,因此这就要求在启动机器人时就必须要启动很多个节点程序,一般的 ROS机器人由十几个节点程序组成,复杂的几十个都有可能。 这就要求我们必须高效率的启动很多节点,而不是通过rosrun命令来依次启动十几个节点程序,launch文件 就是为解决这个需求而生,launch文件就是一个xml格式的脚本文件,我们把需要启动的节点都写进launch文件 中,这样我们就可以通过roslaunch工具来调用launch文件,执行这个脚本文件就一次性启动所有的节点程序。 简述topic 主题是节点间用来传输数据的总线。 通过主题进行消息路由不需要节点之间直接连接——发布者和订阅者之间不需要知道彼此是否存在。 一个主题可以有多个订阅者,也可以有多个发布者。 ROS的主题可以使用TCP/IP和UDP传输:TCPROS(默认),UDPROS(低延迟高效率,数据丢失,远程操作) 话题通讯机制 简述service 发布/订阅模型是一种灵活的通讯机制,但是其多对多、单向传输的方式不适合于分布式系统通常要求的请求/应答交互方式。 请求/应答通过服务实现。服务由一对消息定义:一个用于请求,一个用于应答。 一个节点提供服务名称,客户通过发送请求消息并等待应答来使用服务。 服务通讯机制 简述action 在ros中,如果想要发送一个request给一个节点该节点完成一些任务,并且给出一个回复,这样可以使用ros中的service完成。但是有一些应用中任务执行的时间很长,用户需要查看执行的进度如何,以及或者取消该任务,ros中提供了一个actionlib package来建立服务,执行一个长时间运行的可抢占目标。 可以道是“升级版的service”,算是对service在某些情况下能力不足的一种补充。 在一个复杂的ROS系统中我们会需要各种各有的服务要求,向某个节点发送请求执行某一个任务, 并返回相应的执行结果,这种用ROS的服务(services)完成。然而有一些情况服务执行的时间很长, 在执行中想要获得任务处理的进度,或可能取消执行任务,actionlib就能实现这样的功能,它是ROS的一个非常重要的库,算是对service在某些情况下能力不足的一种补充。 在前面介绍过服务,因为它在执行的过程中是阻塞的,会阻止程序进一步执行,必须等待服务器 返回结果才会继续执行后面的程序,对于一些计算量较轻的任务使用服务是可以满足的。但是对于一 些伏在的任务,例如让机器人从房间的某一点A运动到指定的地点B,在此运动过程中需要花费的时间 未定,任务时间较长而且中途会遇到各种情况,例如机器人电量不足时,这就需要取消或暂停当前任 务等待机器人充满电再继续该任务。 什么是节点?如何启动一个节点? 节点(Nodes) 节点是执行计算的进程。 一个机器人控制系统通常包括很多节点:节点1控制一个激光测距仪,节点2控制电机,节点3进行定位,节点4进行路径规划,节点5提供系统的图形视图(每个节点应具有特定的单一功能、且名称唯一)。 节点使用roscpp和rospy等ROS客户端库进行编写。 rosrun单个启动; roslaunch批量启动; 什么是节点管理器? 节点管理器(Master) 用于节点的注册和查找。 没有节点管理器,节点之间无法发现对方、交换消息或者调用服务。 ROS是一个分布式网络系统,可以在一台计算机上运行节点管理器,在该管理器或其他计算机上运行节点。 简述话题与服务的区别?(8点) 新建一个功能包的命令是什么? catkin_create_pkg [package_name] [depend1] [depend2] [depend3] rqt_console工具的功能是什么? ROS提供了一系列的基于QT的可视化图形调试工具,能够帮助我们快速开发机器人。 rqt_console属于ROS日志框架(logging framework)的一部分,用来显示节点的输出信息。 rqt_logger_level允许我们修改节点运行时输出信息的日志等级(logger levels)(包括 DEBUG、WARN、INFO和ERROR)。 什么是节点重生属性? 节点重生属性(respawn) 当roslaunch开启所有nodes后,roslaunch会监视每个node,记录那些仍然活动的nodes。对于每个node,当其终止后,我们可以要求roslaunch重启该node,通过使用respawn属性。 什么是节点必要属性? 当一个节点被声明为必要节点即 required="true"终止的时候,roslaunch 会终止所有其他活跃节点并退出。比如 在依赖激光雷达的机器人导航中,若激光雷达节点意外退出时,roslaunch将会终止其他节点然后退出。 从控制的角度简述一下机器人的组成? 简述一下传感系统的组成,及常用的设备 什么是URDF?由哪几部分标签组成? URDF是机器人模型描述文件; urdf描述机器人模型问题所在? 模型冗长,重复多; 修改参数麻烦,不便于二次开发; 没有参数计算功能; ………. 什么是xacro模型文件?相比URDF有什么优势? URDF是机器人模型描述文件; xacro模型文件是urdf模型的进化版本; 优势:精简模型代码 创建宏定义; 文件包含;提供可编程接口 常量; 变量; 条件语句; 数学计算。 什么是tf?为什么要有tf?坐标转换(TransForm:位置和姿态) 坐标变换是空间实体的位置描述,是从一种坐标系统变换到另一种坐标系统的过程。通过建立两个坐标系统之间一一对应关系来实现。 为什么要有tf:(个人课堂总结笔记)一个机器人项目一般都是会有许许多多个组件和关节、部位等等,那么整个机器人诸多部件之间是不可能只有一个坐标系的,事实上它们每一个组件每一个部位都有它们自己的坐标系,所以当我们要描述一个坐标系中的物体处在另外一个坐标系的状态是什么样的时候,这时候参数间的转化就需要tf了。 (机器人系统通常具有许多随时间变化的3D坐标系,例如世界坐标系,车体坐标系,手抓坐标系,头部坐标系等。随着时间的推移如何跟踪这些坐标系的变化,提出以下几个问题: 5秒前,头部坐标系相对于世界坐标系在哪里? 机器人手抓中的物体相对于机器人本体基座的姿态是什么? 地图坐标系中机器人的基座当前的姿态是什么?) tf是一个用户随时间跟踪多个坐标系的包,机器人不同部位和世界的坐标系以 tree structure 的形式存储起来,tf管理一系列的树状结构坐标系之间的关系。允许用户在各个坐标系中进行点、向量的变换。通俗的说tf可以帮助我们实时的在各个坐标系中进行坐标转换。 什么是slam? SLAM (simultaneous localization and mapping),也称为CML (Concurrent Mapping and Localization), 即时定位与地图构建,或并发建图与定位。 问题可以描述为:将一个机器人放入未知环境中的未知位置,是否有办法让机器人一边逐步描绘出此环境完全的地图,所谓完全的地图(a consistent map)是指不受障碍行进到房间可进入的每个角落。 slam解决的问题是:“我在哪里,我所处的环境是什么样子?” 在ros里实现slam,对于机器人的硬件要求有什么?为什么? Twist速度控制指令包括哪几个方面? gmapping 算法功能包的输入信息包含什么? gmapping 输出的是什么? gmapping 订阅了什么话题? 简述一下gmapping 算法中的TF变换。 Move_base功能包里由ros提供的部分的核心是什么?(unsure) acml是什么意思?实现原理是什么? Move_base的配置文件有几个?(强化弥补33) 1) 2)base_local_planner_params.yaml该配置文件里面配置了局部路径规划相关的配置。 3)costmap_common_params.yaml 该配置文件里面配置了代价地图共用的参数。 4)global_costmap_params.yaml 该配置文件里面配置了全局代价地图相关参数。 5)local_costmap_params.yaml 该配置文件里面配置了局部代价地图共用的参数。 35.什么是全局代价地图?什么是局部代价地图? 全局代价地图用于全局路径规划,局部代价地图用于本地路径规划和实时避障。 简述一下PR2机器人 简述一下什么是tf树 机器人不同部位和世界的坐标系以 tree structure 的形式存储起来,tf管理一系列的树状结构坐标系之间的关系。 查看功能包路径的命令是什么? rospack find [package_name] CMakeLists.txt的作用是什么? CMakeLists.txt:Cmake的生成文件,编译器编译功能包的规则。 40.package.xml的作用是什么? package.xml:功能包清单。定义有关包的属性,例如包名称,版本号,作者,维护者以及其他catkin包的依赖关系。 功能包目录跳转命令是什么? roscd 什么是gazebo? Gazebo是一个自主机器人3D仿真环境。 它可以与ROS配套用于完整的机器人仿真,也可以单独使用。能够进行动力学仿真等,模拟机器人以及机器人所处的复杂的物理环境。 什么是rviz?rviz是ros的一个可视化工具,用于可视化传感器的数据和状态信息。rviz支持丰富的数据类型,通过加载不同的Dispalys类型来可视化,每一个Dispaly都有一个独特的名字。 gazebo和rviz的区别是什么? gazebo侧重于机器人模型的物理特性以及动力学仿真等和物理环境的模拟,rviz则侧重于传感器的数据和状态信息的可视化,并没有像gazebo那样模拟出形象的物理环境。 gazebo和rviz的区别
2018, 收获良多,感慨良多 感谢所有遇见和美好 感谢所有陪伴和信任 2019,新年,还, 携手并肩,再渡江湖 新年快乐 祝大家新年快乐
一横一竖 一爱一恨 一粗一细 一情一仇 笔墨纸砚 最知人心
CPU定时器0模块初始化: #include "DSP28_Device.h" struct CPUTIMER_VARS CpuTimer0; //对用户开放的CPU定时器只有CpuTimer0,CpuTimer1 struct CPUTIMER_VARS CpuTimer1; //和CpuTimer2被保留用作实习操作系统OS(例如DSP struct CPUTIMER_VARS CpuTimer2; //BIOS) //初始化CpuTimer0。 void InitCpuTimers(void) { CpuTimer0.RegsAddr = &CpuTimer0Regs; //使得CpuTimer0.RegsAddr 指向定时器寄存器 CpuTimer0Regs.PRD.all = 0xFFFFFFFF; //初始化CpuTimer0的周期寄存器 CpuTimer0Regs.TPR.all = 0; //初始化定时器预定标计数器 CpuTimer0Regs.TPRH.all = 0; CpuTimer0Regs.TCR.bit.TSS = 1; //停止定时器 CpuTimer0Regs.TCR.bit.TRB = 1; //将周期寄存器PRD中的值装入计数器寄存器TIM中 CpuTimer0.InterruptCount = 0; //初始化定时器中断计数器 } //Timer(指定的定时器),Freq,Period void ConfigCpuTimer(struct CPUTIMER_VARS *Timer, float Freq, float Period) { Uint32 temp; Timer->CPUFreqInMHz = Freq; Timer->PeriodInUSec = Period; temp = (long) (Freq * Period); Timer->RegsAddr->PRD.all = temp; //给定时器周期寄存器赋值 Timer->RegsAddr->TPR.all = 0; //给定时器预定标寄存器赋值 Timer->RegsAddr->TPRH.all = 0; // 初始化定时器控制寄存器: Timer->RegsAddr->TCR.bit.TIF=1; //清除中断标志位 Timer->RegsAddr->TCR.bit.TSS = 1; //停止定时器 Timer->RegsAddr->TCR.bit.TRB = 1; //定时器重装,将定时器周期寄存器的值装入定时器计数器寄存器 Timer->RegsAddr->TCR.bit.SOFT = 1; Timer->RegsAddr->TCR.bit.FREE = 1; Timer->RegsAddr->TCR.bit.TIE = 1; //使能定时器中断 Timer->InterruptCount = 0; //初始化定时器中断计数器 } 主函数模块: #include "DSP28_Device.h" void main() { InitSysCtrl(); DINT; IER = 0x0000; IFR = 0x0000; InitPieCtrl(); InitPieVectTable(); InitPeripherals(); InitGpio(); PieCtrl.PIEIER1.bit.INTx7 = 1; IER |= M_INT1; EINT; ERTM; ConfigCpuTimer(&CpuTimer0, 150, 1000000); StartCpuTimer0(); for(;;) { } } CPU定时器0周期中断函数: interrupt void TINT0_ISR(void) // CPU-Timer0中断函数 { CpuTimer0.InterruptCount++; if(CpuTimer0.InterruptCount==1) { GpioDataRegs.GPFCLEAR.bit.GPIOF14=1; //XF引脚低电平,D3亮 } if(CpuTimer0.InterruptCount==2) { GpioDataRegs.GPFSET.bit.GPIOF14=1; //XF引脚高电平,D3灭 CpuTimer0.InterruptCount=0; } CpuTimer0Regs.TCR.bit.TIF=1; //清除定时器中断标志位 PieCtrl.PIEACK.bit.ACK1=1; //响应同组其他中断 EINT; //开全局中断 }
系统初始化函数: void InitSysCtrl(void) { Uint16 i; EALLOW; SysCtrlRegs.WDCR = 0x0068; SysCtrlRegs.PLLCR = 0xA; for(i=0;i<5000;i++){} SysCtrlRegs.HISPCP.all = 0x0001; SysCtrlRegs.LOSPCP.all = 0x0002; SysCtrlRegs.PCLKCR.bit.EVAENCLK = 1; SysCtrlRegs.PCLKCR.bit.EVBENCLK = 1; SysCtrlRegs.PCLKCR.bit.SCIENCLKA = 1; EDIS; } GPIO初始化函数: void InitGpio(void) { EALLOW; GpioMuxRegs.GPAMUX.bit.T1PWM_GPIOA6 = 1; GpioMuxRegs.GPAMUX.bit.T2PWM_GPIOA7 = 1; GpioMuxRegs.GPAMUX.bit.PWM1_GPIOA0=1; GpioMuxRegs.GPAMUX.bit.PWM2_GPIOA1=1; GpioMuxRegs.GPAMUX.bit.PWM3_GPIOA2=1; GpioMuxRegs.GPAMUX.bit.PWM4_GPIOA3=1; GpioMuxRegs.GPAMUX.bit.PWM5_GPIOA4=1; GpioMuxRegs.GPAMUX.bit.PWM6_GPIOA5=1; GpioMuxRegs.GPBMUX.bit.T3PWM_GPIOB6=1; GpioMuxRegs.GPBMUX.bit.T4PWM_GPIOB7=1; GpioMuxRegs.GPBMUX.bit.PWM7_GPIOB0=1; GpioMuxRegs.GPBMUX.bit.PWM8_GPIOB1=1; GpioMuxRegs.GPBMUX.bit.PWM9_GPIOB2=1; GpioMuxRegs.GPBMUX.bit.PWM10_GPIOB3=1; GpioMuxRegs.GPBMUX.bit.PWM11_GPIOB4=1; GpioMuxRegs.GPBMUX.bit.PWM12_GPIOB5=1; EDIS; } 初始化EV函数: void InitEv(void) { EvaRegs.T1CON.bit.TMODE=2; EvaRegs.T1CON.bit.TPS= 1; EvaRegs.T1CON.bit.TENABLE=0; EvaRegs.T1CON.bit.TCLKS10=0; EvaRegs.T1CON.bit.TECMPR=1; EvaRegs.T2CON.bit.TMODE=2; EvaRegs.T2CON.bit.TPS =1; EvaRegs.T2CON.bit.TENABLE=0; EvaRegs.T2CON.bit.TCLKS10=0; EvaRegs.T2CON.bit.TECMPR=1; EvaRegs.GPTCONA.bit.TCOMPOE=1; EvaRegs.GPTCONA.bit.T1PIN=1; EvaRegs.GPTCONA.bit.T2PIN=2; EvaRegs.T1PR=0x927B; EvaRegs.T1CMPR=0x3A98; EvaRegs.T1CNT=0; EvaRegs.T2PR=0x927B; EvaRegs.T2CMPR=0x57E4; EvaRegs.T2CNT=0; EvaRegs.COMCONA.bit.CENABLE=1; EvaRegs.COMCONA.bit.FCOMPOE=1; EvaRegs.COMCONA.bit.CLD=2; EvaRegs.DBTCONA.bit.DBT=10; EvaRegs.DBTCONA.bit.EDBT1=1; EvaRegs.DBTCONA.bit.EDBT2=1; EvaRegs.DBTCONA.bit.EDBT3=1; EvaRegs.DBTCONA.bit.DBTPS=4; EvaRegs.ACTR.all=0x0999; EvaRegs.CMPR1=0x3A98; EvaRegs.CMPR2=0x3A98; EvaRegs.CMPR3=0x3A98; EvbRegs.T3CON.bit.TMODE=1; EvbRegs.T3CON.bit.TPS=1; EvbRegs.T3CON.bit.TENABLE=0; EvbRegs.T3CON.bit.TCLKS10=0; EvbRegs.T3CON.bit.TECMPR=1; EvbRegs.T4CON.bit.TMODE=1; EvbRegs.T4CON.bit.TPS=1; EvbRegs.T4CON.bit.TENABLE=0; EvbRegs.T4CON.bit.TCLKS10=0; EvbRegs.T4CON.bit.TECMPR=1; EvbRegs.GPTCONB.bit.TCOMPOE=1; EvbRegs.GPTCONB.bit.T3PIN=1; EvbRegs.GPTCONB.bit.T4PIN=2; EvbRegs.T3PR=0x493E; EvbRegs.T3CMPR=0x1D4C; EvbRegs.T3CNT=0; EvbRegs.T4PR=0x493E; EvbRegs.T4CMPR=0x2BF2; EvbRegs.T4CNT=0; EvbRegs.COMCONB.bit.CENABLE=1; EvbRegs.COMCONB.bit.FCOMPOE=1; EvbRegs.COMCONB.bit.CLD=2; EvbRegs.DBTCONB.bit.DBT=10; EvbRegs.DBTCONB.bit.EDBT1=1; EvbRegs.DBTCONB.bit.EDBT2=1; EvbRegs.DBTCONB.bit.EDBT3=1; EvbRegs.DBTCONB.bit.DBTPS=4; EvbRegs.ACTRB.all=0x0999; EvbRegs.CMPR4=0x1D4C; EvbRegs.CMPR5=0x1D4C; EvbRegs.CMPR6=0x1D4C; } 主函数: void main(void) { InitSysCtrl(); DINT; IER=0x0000; IFR=0x0000; InitPieCtrl(); InitPieVectTable(); InitGpio(); InitEv(); EvaRegs.T1CON.bit.TENABLE=1; EvaRegs.T2CON.bit.TENABLE=1; EvbRegs.T3CON.bit.TENABLE=1; EvbRegs.T4CON.bit.TENABLE=1; } Debug: "DSP28_Gpio.c", line 51: warning: last line of file ends without a newline 文件的最后一行没有换行符结束
转自原文:https://wenku.baidu.com/view/919a0f9da1c7aa00b42acb3d.html
关于今天的一个关于ASP的课后作业,是要求在ASP上实现随机生成数字序列: 具体要求: 随机位置:每个数字的位置相对随机; 随机颜色:每个数字的颜色随机且不重复; 随机数字:从0到9随机取出四个数; 正文 首先放上核心算法,这里我觉得在common.cs中编写比较妥当: public static int[] GetRandom(int minValue, int maxValue, int count) { int[] intList = new int[maxValue];//创建一个以 最大值大小 为长度的数组 for (int i = 0; i < maxValue; i++)//数组的内容:最小值+(从 0 到 最大值减一 ),及intList为一个特殊规律的不重复的递增数组 { intList[i] = i + minValue; } int[] intRet = new int[count];//创建以 要取的数的个数 为长度的数组 int n = maxValue; Random rand = new Random(); for (int i = 0; i < count; i++) { int index = rand.Next(0, n);//随机取一个0到n之间的数 intRet[i] = intList[index]; intList[index] = intList[--n]; } return intRet; } //n是一个递减变化的数 //intList的一个运行模拟序列: //0 1 2 3 4 n = listlength = 5,取到1 //0 4 2 3 | 4 n = listlength = 4,取到4 //0 3 2 | 3 4 n = listlength = 3 //... //不断用最后面的值来覆盖选中到的值,再把最后面的值去掉(通过n--实现,抽象意义上“截短”提供数字的intList),由此实现不重复序列 详细解析见以上的代码截图。 接着是.aspx.cs文件(下图为部分剪影,后方附上完整代码): using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using System.Data; using System.Text; using System.IO; using System.Drawing; public partial class Default2 : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { Random rd = new Random(); int num = rd.Next(1000,10000); string textString = num.ToString(); int widthx = 800; int heightx = 600; Bitmap btmap = new Bitmap(widthx, heightx); Graphics gg = Graphics.FromImage(btmap); SolidBrush sb = new SolidBrush(Color.White); gg.FillRectangle(sb, new Rectangle(0, 0, widthx, heightx)); Font ft = new Font("楷体",18,FontStyle.Strikeout); SolidBrush sbft = new SolidBrush(Color.Black); Color[] cr = {Color.Red,Color.Black,Color.Blue,Color.Yellow,Color.Gray,Color.Orange}; //gg.DrawString(textString.Substring(0,textString.Length/2), ft , sbft, new PointF(0,0)); //gg.DrawString(textString.Substring(5,5), ft, sbft1, new PointF(0, 300)); int[] rdlist = common.GetRandom(0,cr.Length,textString.Length);//产生一个随机的不重复的int列表 int leftmargin = 0; for (int i=0; i < textString.Length; i++) { //使用时,顺序对这个int列表取值即可 gg.DrawString(textString.Substring(i,1),ft,new SolidBrush(cr[rdlist[i]]),leftmargin,leftmargin+100+rd.Next(-15,15)); leftmargin = leftmargin + 18; } MemoryStream ms = new MemoryStream(); btmap.Save(ms, System.Drawing.Imaging.ImageFormat.Gif); Response.ClearContent(); Response.ContentType = "image/gif"; Response.BinaryWrite(ms.ToArray()); } } 至此便实现了要求了,下面放上效果图: 算法参考文章
“美国经济学家 诺奖得主西奥多·舒尔茨 研究结果:当社会的平均受教育水平更高的时候,贫富差距会越来越小——一旦共享知识了,人人变得更博学了,那个这个世界,将会每个角落的贫富差距都会在缩小,寒门将出更多的贵子” 人类文明最大的教训,就是对技术的警惕,对人性的宽容; 人类文明最大的经验,就是对技术的宽容,对人性的警惕。 ——杨奇函 当我们认为自己是否博学的时候,我们的脑中包括我们是不是累积了更多的智慧,而智慧包括了我们对于知识的理解方式跟使用方式,这个是可以每天进步的 ——蔡康永 “如果我们针对少部分人的不劳而获叫做特权,针对多数人和全部人的不劳而获,我们叫做它文明” ——高庆一 “我们为什么要把各位请到这里来坐着,就是我希望用他们的语言,来打开他们那个世界的一扇窗户,让我们看一看那个世界当中的风景,而今天,知识共享,能够不是推开窗户,是让我们每一个世界的门打开,我们会走到一个又一个的平行五彩斑斓的世界当中去,看同一个地球,看到的是无数个平行世界,你们就不想进去看一看吗!首尔,美不美呢!” “好最后我说,一个非常实在的观点,我们为什么要知识共享,非常实在,因为它可以,救人的命......我只希望告诉大家,走到每一个美丽的新世界当中去,而且让身边的生命逝去的越来越少,这样的世界你们,值得拥有” ——陈铭(1/9---->>>6/4——“但是没有死结,只要给我时间,我就能解开”) “我们今天不是活在一个知识被锁在庙堂里的时代,我们活在一个资讯爆炸的时代,我们活在一个我们生产知识的速度超越我们储存它速度的时代,所以,我们这个时代考验的恰恰是你从这些知识当中去挑选、辨别、排序的能力,这种能力是我熟能生巧的一种直觉,它是不可以被存进芯片的,而那个芯片,让你误以为你懂得了一切的芯片,它给你的是不必要的知识,它拿走的,是你必要的智识。” 进步来源于对知识的否定。 “事实上,人类的知识之所以能够不停进步,是因为我们幸运的,在历史上,从不曾有过一张芯片,一个人,一个权威,一个团体,可以替所有的人拍板——什么是对,什么是真,什么是知识。” ——詹青云 专利制度避免了低效和浪费 知识无取舍则无意义 “总是使一个国家变成人间地狱的东西,恰恰是人们试图将其变成天堂。”——荷尔德林 “非要在人间建天堂,会在人间建出地狱来。”——李诞
时钟 STM32F10xxx参考手册中文版 6.2 时钟 peripheral n.外部设备PCLK1 低速外设时钟PCLK2 高速外设时钟RC 阻容...振荡器 RTC Real time clock 实时时钟 prescaler 预分频器;预定标器/x 即X分频PLLSRC 选择信号TIM 定时器TIM2,3,4,5,6,7由APB1决定(判断逻辑选择如图)TIMxCLK 通用定时器时钟 TIM1,8由APB2决定(判断逻辑选择如图)TIM1 and TIM8高级定时器时钟 HSION 内部高速时钟开启HSEON 外部高速时钟开启PLLON 锁相环开启 HSEBYP BYP 即by past旁路 旁路指EXTERNAL SOURCE直接输入,不经过OSC_OUT HSERDY 用于去抖 HSICAL8位加HSITRIM5位用来调整HSI频率 机器一开始会出现波形抖动,不稳定不可用,一段时间后才会变成可用的稳定的方波 波形抖动阶段,RDY位置 0 波形抖动结束,进入可用状态,RDY位则置1 所谓旁路,就是没有经过PLL(如下所示就是一条旁路): 内部RC高速时钟一般不准确,准确性远不如外部晶振,对温度敏感性较强,需要对其进行调整。 如果HSE晶体振荡器失效,HSI时钟会被作为备用时钟 重点!RCC_CFGRMCO 选择时钟源 看门狗监控运行的程序,程序出了问题,令之重启 APB(Advanced Peripheral Bus),外围总线的意思。<百度百科> CSS 时钟安全系统 ITF Interface 高级控制定时器 互补PWM: 就是两组PWM信号,它们的波形是互补的,简单理解,就是这两个信号直接相加的话,结果是一条直线。互补PWM有时候需要增加一个“死区”,防止两个信号同时为1或者同时为0的瞬间出现。 寄生电容: "寄生电容" 在学术文献中的解释 1、另一方面传感器除有极板间电容外,极板与周围体(各种元件甚至人体)也产生电容联系,极板之间空隙的空气,这种电容称为寄生电容。它不但改变了电容传感器的电容量,而且由于传感器本身电容量很小,寄生电容极不稳定,这也导致传感器特性不稳定,对传感器产生严重干扰。 2、分布在导线之间、线圈与机壳之间以及某些元件之间的分布电容等,这些电容称为寄生电容,它们的数值虽小,但是却是引起干扰的重要原因。 PWM abbr. Pulse-Width Modulation 脉宽调制; PR 周期 ck_cnt clock count 时钟计数 IGBT IGBT(Insulated Gate Bipolar Transistor),绝缘栅双极型晶体管,是由BJT(双极型三极管)和MOS(绝缘栅型场效应管)组成的复合全控型电压驱动式功率半导体器件,兼有MOSFET的高输入阻抗和GTR的低导通压降两方面的优点。 GTR饱和压降低,载流密度大,但驱动电流较大;MOSFET驱动功率很小,开关速度快,但导通压降大,载流密度小。IGBT综合了以上两种器件的优点,驱动功率小而饱和压降低。 非常适合应用于直流电压为600V及以上的变流系统如交流电机、变频器、开关电源、照明电路、牵引传动等领域。 PCR设定分频器的值, 等到计数寄存器计数完毕, 产生一个UEV, 随后设定的分频器参数即可启动 Auto reload register 自动重新加载寄存器TIMx_ARR Repetition 重复次数寄存器TIMx_RCR 关于影子寄存器的作用: 设计预装载寄存器和影子寄存器的好处是,所有真正需要起作用的寄存器(影子寄存器)可以在同一个时间(发生更新事件时)被更新为所对应的预装载寄存器的内容,这样可以保证多个通道的操作能够准确地同步。如果没有影子寄存器,软件更新预装载寄存器时,则同时更新了真正操作的寄存器,因为软件不可能在一个相同的时刻同时更新多个寄存器,结果造成多个通道的时序不能同步,如果再加上例如中断等其它因素,多个通道的时序关系有可能会混乱,造成是不可预知的结果。
uart8051.h: #ifndef __UART_H__ #define __UART_H__ #define XTAL 12000000 #define baudrate 9600 char UART_putchar(char ch); char UART_getkey(void); void InitCom(void); #endif uart8051.c: #include <reg51.h> #include "uart8051.h" char UART_putchar(char ch) { if(ch=='\n') { SBUF = 0x0d; while(TI == 0); TI=0; SBUF = 0x0a; } else SBUF = ch; while(TI == 0); TI = 0; return ch; } char UART_getkey(void) { char c; while(!RI); c = SBUF; RI = 0; return(c); } void InitCom(void) { SCON=0X50; TMOD=0X20; PCON=0X80; TH1=0XF3; TL1=0XF3; ES=0; EA=1; TR1=1; } main.c: #include <reg51.h> #include <stdio.h> #include "uart8051.h" char putchar(char ch) { return UART_putchar(ch); } char _getkey(void) { return UART_getkey(); } void main(void) { unsigned int tem; InitCom(); printf("´®¿Ú²âÊÔ£¬³£¹æÊ¹ÓÃ:\n"); UART_putchar('A');UART_putchar(' '); UART_putchar('B');UART_putchar(' '); UART_putchar('C');UART_putchar(' '); UART_putchar('D');UART_putchar(' '); UART_putchar('\n'); printf("Ç¿´óµÄ¸ñʽ»¯Á÷Êä³ö£º\n"); printf("Êä³öÒ»¸öÕûÐΣº%d\n",(int)6886); printf("Êä³öÒ»¸öʵÐÍ£º%d\n",(float)68.86); while(1) { printf("\nÊäÈëÒ»¸öÕûÐÍ£¬ËÍP2¿Ú×¢Òâ¹Û²ìP2¿ÚµÄµÆ:\n"); scanf("%d",&tem); P2=tem; } } 编译之后,打开烧录软件烧录,然后打开串口通信助手进行调试: 如上图,分别输入1+空格,2+空格,3+空格,效果图如下: 1+空格: 2+空格: 3+空格:
sudo apt-get install ros-kinetic-gazebo-ros-pkgs ros-kinetic-gazebo-ros-control 以上是一句安装ros一个插件的语句, 运行时需要获得锁 /var/lib/dpkg/lock; 没有获得锁时,会出现“无法锁定管理目录(/var/lib/dpkg/),是否有其他进程正占用它”的报错。 解决方法: :sudo rm /var/cache/apt/archives/lock :sudo rm /var/lib/dpkg/lock 解决历程剪影: liweipeng@liweipeng-virtual-machine:~$ sudo apt-get install ros-kinetic-gazebo-ros-pkgs ros-kinetic-gazebo-ros-control [sudo] liweipeng 的密码: E: 无法获得锁 /var/lib/dpkg/lock - open (11: 资源暂时不可用) E: 无法锁定管理目录(/var/lib/dpkg/),是否有其他进程正占用它? liweipeng@liweipeng-virtual-machine:~$ sudo rm /var/cache/apt/archives/lock liweipeng@liweipeng-virtual-machine:~$ sudo rm /var/lib/dpkg/lock liweipeng@liweipeng-virtual-machine:~$ sudo apt-get install ros-kinetic-gazebo-ros-pkgs ros-kinetic-gazebo-ros-control [sudo] liweipeng 的密码: 正在读取软件包列表... 完成 正在分析软件包的依赖关系树 正在读取状态信息... 完成 下列软件包是自动安装的并且现在不需要了: linux-headers-4.15.0-29 linux-headers-4.15.0-29-generic linux-image-4.15.0-29-generic linux-modules-4.15.0-29-generic linux-modules-extra-4.15.0-29-generic 使用'sudo apt autoremove'来卸载它(它们)。 下列软件包将被升级: ros-kinetic-gazebo-ros-control ros-kinetic-gazebo-ros-pkgs 升级了 2 个软件包,新安装了 0 个软件包,要卸载 0 个软件包,有 282 个软件包未被升级。 需要下载 141 kB 的归档。 解压缩后会消耗 0 B 的额外空间。 获取:1 http://packages.ros.org/ros/ubuntu xenial/main amd64 ros-kinetic-gazebo-ros-control amd64 2.5.17-0xenial-20181107-062456-0800 [139 kB] 获取:2 http://packages.ros.org/ros/ubuntu xenial/main amd64 ros-kinetic-gazebo-ros-pkgs amd64 2.5.17-0xenial-20181107-045216-0800 [2,834 B] 已下载 141 kB,耗时 3秒 (45.4 kB/s) (正在读取数据库 ... 系统当前共安装有 329087 个文件和目录。) 正准备解包 .../ros-kinetic-gazebo-ros-control_2.5.17-0xenial-20181107-062456-0800_amd64.deb ... 正在将 ros-kinetic-gazebo-ros-control (2.5.17-0xenial-20181107-062456-0800) 解包到 (2.5.17-0xenial-20180824-143032-0800) 上 ... 正准备解包 .../ros-kinetic-gazebo-ros-pkgs_2.5.17-0xenial-20181107-045216-0800_amd64.deb ... 正在将 ros-kinetic-gazebo-ros-pkgs (2.5.17-0xenial-20181107-045216-0800) 解包到 (2.5.17-0xenial-20180824-174334-0800) 上 ... 正在设置 ros-kinetic-gazebo-ros-control (2.5.17-0xenial-20181107-062456-0800) ... 正在设置 ros-kinetic-gazebo-ros-pkgs (2.5.17-0xenial-20181107-045216-0800) ... liweipeng@liweipeng-virtual-machine:~$ sudo apt-get install ros-kinetic-gazebo-ros-pkgs ros-kinetic-gazebo-ros-control 正在读取软件包列表... 完成 正在分析软件包的依赖关系树 正在读取状态信息... 完成 ros-kinetic-gazebo-ros-control 已经是最新版 (2.5.17-0xenial-20181107-062456-0800)。 ros-kinetic-gazebo-ros-pkgs 已经是最新版 (2.5.17-0xenial-20181107-045216-0800)。 下列软件包是自动安装的并且现在不需要了: linux-headers-4.15.0-29 linux-headers-4.15.0-29-generic linux-image-4.15.0-29-generic linux-modules-4.15.0-29-generic linux-modules-extra-4.15.0-29-generic 使用'sudo apt autoremove'来卸载它(它们)。 升级了 0 个软件包,新安装了 0 个软件包,要卸载 0 个软件包,有 282 个软件包未被升级。 liweipeng@liweipeng-virtual-machine:~$ 解决方法参考自:https://blog.csdn.net/chenqiai0/article/details/8514945
Android 实战 | 使用揭露动画(Reveal Effect)做一个丝滑的Activity转场动画 揭露动画(Reveal Effect)实现时的注意事项(附上bug-logcat) boolean java.lang.String.equals(java.lang.Object)' on a null object reference java.lang.NullPointerException: Attempt to invoke virtual method 'int android.content.Intent.getI... 其他 ROS_机器人urdf建模仿真实践 基于VM14+ Ubuntu 16.04安装VMware Tools(VM同主机file交互的工具)以及使用的骚操作 解决 | 此数据库文件跟当前sql server实例不兼容 & sql server2008无法连接到(local) 解决 | VS 2015右键项目添加新项中没有web窗体等选项
提笔之际(附总体思路) 最近跟几个小伙伴在实践一个项目,考虑到界面效果,我们决定使用揭露动画作为Activity的转场动画。 这里主要是我负责这部分的实现。 话说之前是没接触过的,关于具体的实现跟大体的思路都不太清楚。于是最先啃官方API,有点难看懂,然后下载了官方的demo,直接看代码,还是有问题,毕竟它规模略大,集成了好多动画效果; 接着就找了很多博文,发现网上真的水文忒多了哎。。 最后找到了这三篇,算是解答了我的疑问: https://www.jianshu.com/p/b75548e488df 这篇思路很好,写得也很走心,启发了我设计的思路跟注意到的一些问题,像揭露动画的逻辑放在哪里之类的;正当我要下载demo的时候发现他代码是kotlin来的,好吧,再继续找博文; https://blog.csdn.net/shedoor/article/details/81251849#5 这篇就讲得更加详尽,这还看不懂那就有点说不过去了。一开始觉得揭露动画还是挺高大上的样子,结果此博文文首便说很简单,那就很简单吧,后来理解透了之后发觉确实也不难,毕竟只有一个静态方法而已;https://github.com/OCNYang/Android-Animation-Set/tree/master/reveal-animation 这个点进去是他的GitHub,demo下下来,代码看一下,自己写个小demo(我是先在一个activity里面跑通揭露动画,再进一步将揭露动画实现成跳转动画),再加入自己的逻辑,一步一步来算是没有问题了。 到这里就跑通了一个活动中的Activity了; https://github.com/whyalwaysmea/AndroidDemos 接下来就进入本文主题了,使用揭露动画作为Activity的转场动画; 这篇文档跟代码算是帮上大忙了,有较大的参考价值; 不同的是作者的思路是在跳转的目标活动中,启动做揭露动画的收挽,收挽结束后再finish(); 我这里根据情况修改为跳转的目标活动中按下返回键即finish(),完了之后原始活动中的onReStart()中做揭露动画的收挽;另外我在在跳转的目标活动中完成揭露动画展开的时候,添加了一个AlphaAnimation; 这边的起始活动用的是button的onClick触发的方式,以及这里对两个活动各自的控件的visible做了细节的把控; 最终效果图 GitHub中附方法详解图 引子 使用揭露动画做一个丝滑的Activity转场动画, 关于这个需求,可能不同的同学,会有不同的问题, 我这里把可能遇到的问题跟我在完成这个demo的过程中遇到的问题做一个总结, 然后附上总体的思路,大家可以交流一下~ 什么是揭露动画?Material-Animations; 官网有详细的介绍, 揭露动画具有相当丝滑的效果, 常常可以用与基于一个Activity的碎片切换或者View、控件的切换覆盖铺张,如本文第一个demo; 或者直接作为两个Activity之间的转场动画,如本文第二个demo; 揭露动画怎么用?官方API封装好了, 一个类一个静态方法——ViewAnimationUtils.createCircularReveal(), 传进五个参数,返回一个Animator对象。根据具体情况调用即可。详细可见参考文档; “丝滑”之解这个转场动画要实现得丝滑,需要注意几个细节:活动A跳转到活动B的情况下, a.在A点击触发跳转时刻,揭露动画要放在哪个活动展开; b.在B按下返回键之后,揭露动画又要放在哪个活动收挽; c.揭露动画的展开和收挽,createCircularReveal()分别以谁为操作对象; d.这里A通过FloatingActionButton出发,那揭露层View跟FloatingActionButton的visible跟invisible设置的顺序; e.关闭android默认的activity转场动画(不然就相当不丝滑了hhh); 相关解答详解下方第二个demo的思路总结,请移步到正文中的第二个demo; 了解本文的两个demo之后,我相信以这个两个demo为模板,结合笔者之前关于Material Design做的诸多笔记,应该是可以做出不少很有趣的东西来的~ 再附上在做本demo的过程中一些debugExperience: 揭露动画(Reveal Effect)实现时的注意事项(附上bug-logcat) boolean java.lang.String.equals(java.lang.Object)' on a null object reference java.lang.NullPointerException: Attempt to invoke virtual method 'int android.content.Intent.getI... 正文 1.先在一个activity里面跑通揭露动画 首先新建一个空项目,接着走三步即可: 1)更改styles.xml,改成NoActionBar,去掉标题栏,待会儿调试可以观察: 2)书写activity_main.xml: 这里对子空间的布局范围要求并不多,直接简单用FrameLayout即可; 然后是Textview放在最前面,最先渲染,以为至底层; 接着我们这里使用一个View原生控件来作为揭露动画的操作对象,即通过对View控件的显示和隐藏以及动画操作来具体实现揭露动画; 最后放置一个悬浮按钮,用于启动点击事件,这里响应的事件是启动揭露动画: 另外说一下,关于FloatingActionButton, android:backgroundTint可以设置其背景色, android:src则给按钮设置图标, 这里用的图标资源来自于阿里的矢量图标库。 <?xml version="1.0" encoding="utf-8"?> <FrameLayout 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.lwp.justtest.MainActivity"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" android:layout_gravity="center" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <View android:id="@+id/view_puppet" android:background="@color/colorPrimaryDark" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="gone"/> <android.support.design.widget.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" android:layout_margin="16dp" android:backgroundTint="@color/colorPrimary" android:src="@drawable/map" app:pressedTranslationZ="10dp" /> </FrameLayout> 3)书写MainActivity.java: 实例化各个组件之后,实现FloatingActionButton的onClick(), onClick()中我们调用一个自定义方法,在里面启动揭露动画; 这里通过变量flag实现点击按钮时揭露动画的交替开启显示以及关闭隐藏,效果图在下方代码之后; 关于揭露动画的逻辑以及具体实现的语法, 其实核心就是ViewAnimationUtils.createCircularReveal()这个方法以及其五个参数的意义, 详细地可以参考前面提到的参考文章链接: package com.lwp.justtest; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.support.design.widget.FloatingActionButton; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.view.ViewAnimationUtils; public class MainActivity extends AppCompatActivity implements View.OnClickListener { boolean flag = false; FloatingActionButton fab; private View mPuppet; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mPuppet = findViewById(R.id.view_puppet); fab = (FloatingActionButton)findViewById(R.id.fab); fab.setOnClickListener(this); } @Override public void onClick(View v) { doRevealAnimation(); } private void doRevealAnimation() { int[] vLocation = new int[2]; fab.getLocationInWindow(vLocation); int centerX = vLocation[0] + fab.getMeasuredWidth() / 2; int centerY = vLocation[1] + fab.getMeasuredHeight() / 2; int height = mPuppet.getHeight(); int width = mPuppet.getWidth(); int maxRradius = (int) Math.hypot(height, width); Log.e("hei", maxRradius + ""); if (flag) { Animator animator = ViewAnimationUtils.createCircularReveal(mPuppet, centerX, centerY, maxRradius, 0); animator.setDuration(1000); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); mPuppet.setVisibility(View.GONE); } }); animator.start(); flag = false; } else { Animator animator = ViewAnimationUtils.createCircularReveal(mPuppet, centerX, centerY, 0, maxRradius); animator.setDuration(1000); animator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { mPuppet.setVisibility(View.VISIBLE); } @Override public void onAnimationEnd(Animator animation) { } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); animator.start(); flag = true; } } } 上面三步走完,即可运行程序,揭露动画随即实现,效果如下: 接着后面就进入本文主题了; 2.使用揭露动画作为Activity的转场动画 思路总结: 1. MainActivity.java: 1.1. 实例化、声明各种对象,注意: 根布局对象(用来控制整个布局), 揭露层对象指的是用于作揭露操作的纯色的match_parent的View控件; 1.2. onCreate():完成findViewById()以及intent的构建,FloatingActionButton的setOnClickListener; 1.3. onClick():计算fab的中心坐标,用于作为揭露动画的圆心;同时把这对坐标put进intent中,然后startActivity(intent);跳转到下一个活动,同时把坐标对传过去; 1.4. createRevealAnimator():计算startRadius、endRadius,调用核心方法createCircularReveal()构建出animator并做相关配置后return之; 注意: 这里的createCircularReveal()操作对象用的是揭露层纯色View对象mPuppet0; 以及配置中用了animator.addListener(animatorListener0);添加一个动画监听器;--->> 1.5. 1.5. Animator.AnimatorListener animatorListener0 注意这里的思路: !!! onAnimationStart():收挽揭露动画开启时,揭露层setVisibility(View.VISIBLE);fab.setVisibility(View.INVISIBLE); onAnimationEnd():收挽版揭露动画结束时,mPuppet0.setVisibility(View.INVISIBLE);fab.setVisibility(View.VISIBLE); !!! 1.6. onRestart():回调方法,计算fab的中心坐标,用于作为揭露动画的圆心; 调用createRevealAnimator()创建并配置一个animator(--->> 1.4.), 然后开启收挽版揭露动画,即animator.start(); 2. next.java: 2.1. 实例化、声明各种对象,注意: 根布局对象(用来控制整个布局), 揭露层对象指的是用于作揭露操作的纯色的match_parent的View控件; 2.2. onCreate():完成findViewById(), 这里注意: 动画需要依赖于某个视图才可启动,这里依赖于根布局对象并且开辟一个子线程, 在子线程中get坐标对,调用createRevealAnimator()创建并配置一个animator;--->> 2.3. 然后开启展开版揭露动画,即animator.start(); 2.3. createRevealAnimator():计算startRadius、endRadius,调用核心方法createCircularReveal()构建出animator并做相关配置后return之; 注意这里的思路: !!!这里的createCircularReveal()操作对象用的是根布局对象content; !!!!! (即先加载好整个布局,再把整个布局作为揭露对象从0径到屏幕对角线径揭露展开, 展开过程中揭露层纯色view在最顶层,所以感觉是View在做展开而已, 而实际上并不是;展开完毕后,再把view层去掉,去掉之后下层的活动内容自然就显示出来了。) !!!!! 以及配置中用了animator.addListener(animatorListener0);添加一个动画监听器;--->> 2.4. 2.4. Animator.AnimatorListener animatorListener1 注意这里的思路: !!! onAnimationEnd():展开版揭露动画结束时, mPuppet.startAnimation(createAlphaAnimation());//调用透明度动画,丝滑效果-->>2.5. mPuppet.setVisibility(View.INVISIBLE);//动画结束时,揭露动画设置为不可见 !!! 2.5. createAlphaAnimation():定义透明度动画,返回一个AlphaAnimation对象; 3. 两个布局xml没什么特别需要说的地方, 注意第一个xml的view要android:visibility="gone",以及按照渲染先后层次关系按序书写控件即是; 4. styles.xml:android:windowAnimationStyle属性置为null,取消掉Android默认的转场动画 <style name="noAnimTheme" parent="AppTheme"> <item name="android:windowAnimationStyle">@null</item> </style> 5. AndroidManifest.xml:为参与的活动添加刚刚设置好的主题; <activity android:name=".MainActivity" android:theme="@style/noAnimTheme"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".next" android:theme="@style/noAnimTheme"> </activity> 最后上代码了,不同的功能基本上都放在了不同的方法内实现,结合注释应该不难理解了~ MainActivity.java: package com.lwp.justtest; import ... public class MainActivity extends AppCompatActivity implements View.OnClickListener { FloatingActionButton fab; Intent intent; private View content;//根布局对象(用来控制整个布局) private View mPuppet0;//揭露层对象 private int centerX; private int centerY; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); content = findViewById(R.id.reveal_content0); mPuppet0 = findViewById(R.id.view_puppet); intent = new Intent(MainActivity.this, next.class); fab = (FloatingActionButton)findViewById(R.id.fab); fab.setOnClickListener(this); } @Override public void onClick(View v) { int[] vLocation = new int[2]; fab.getLocationInWindow(vLocation); centerX = vLocation[0] + fab.getMeasuredWidth() / 2; centerY = vLocation[1] + fab.getMeasuredHeight() / 2; intent.putExtra("cx",centerX); intent.putExtra("cy",centerY); startActivity(intent); } private Animator createRevealAnimator(int x, int y) { float startRadius = (float) Math.hypot(content.getHeight(), content.getWidth()); float endRadius = fab.getMeasuredWidth() / 2 ; //注意揭露动画开启时是用根布局作为操作对象,关闭时用揭露层作为操作对象 Animator animator = ViewAnimationUtils.createCircularReveal( mPuppet0, x, y, startRadius, endRadius); animator.setDuration(500); animator.setInterpolator(new AccelerateDecelerateInterpolator());//设置插值器 animator.addListener(animatorListener0); return animator; } //定义动画状态监听器_按下返回键版 private Animator.AnimatorListener animatorListener0 = new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { mPuppet0.setVisibility(View.VISIBLE);//按下返回键时,动画开启,揭露层设置为可见 fab.setVisibility(View.INVISIBLE); } @Override public void onAnimationEnd(Animator animation) { mPuppet0.setVisibility(View.INVISIBLE); fab.setVisibility(View.VISIBLE); } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }; //第二个活动退回来时,回调本方法 @Override protected void onRestart() { super.onRestart(); //动画需要依赖于某个视图才可启动, // 这里依赖于根布局对象,并且开辟一个子线程,充分利用资源 content.post(new Runnable() { @Override public void run() { int[] vLocation = new int[2]; fab.getLocationInWindow(vLocation); centerX = vLocation[0] + fab.getMeasuredWidth() / 2; centerY = vLocation[1] + fab.getMeasuredHeight() / 2; Animator animator = createRevealAnimator(centerX, centerY); animator.start(); } }); } } main.xml: <?xml version="1.0" encoding="utf-8"?> <FrameLayout 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:id="@+id/reveal_content0" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.lwp.justtest.MainActivity"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Here is First Activity!" android:textColor="@color/colorPrimaryDark" android:textSize="30sp" android:layout_gravity="center" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <View android:id="@+id/view_puppet" android:background="@color/colorPrimaryDark" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="gone"/> <android.support.design.widget.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" android:layout_margin="16dp" android:backgroundTint="@color/colorPrimary" android:src="@drawable/map" app:pressedTranslationZ="10dp" /> </FrameLayout> next.java: package com.lwp.justtest; import ... public class next extends AppCompatActivity { private View content;//根布局对象(用来控制整个布局) private View mPuppet;//揭露层对象 private int mX ; private int mY ; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_next);//先加载好整个布局,后面再用整个布局作为揭露动画的操作对象,揭露完毕后再去掉揭露层 content = findViewById(R.id.reveal_content); mPuppet = findViewById(R.id.view_puppet); //动画需要依赖于某个视图才可启动, // 这里依赖于根布局对象,并且开辟一个子线程,充分利用资源 content.post(new Runnable() { @Override public void run() { mX = getIntent().getIntExtra("cx", 0); mY = getIntent().getIntExtra("cy", 0); Animator animator = createRevealAnimator(mX, mY); animator.start(); } }); } private Animator createRevealAnimator(int x, int y) { float startRadius = 0; float endRadius = (float) Math.hypot(content.getHeight(), content.getWidth()); Animator animator = ViewAnimationUtils.createCircularReveal( content, x, y, startRadius, endRadius); animator.setDuration(660); animator.setInterpolator(new AccelerateDecelerateInterpolator()); //判断标志位reversed,true则为添加返回键版动画监听器,false则为跳转动画开启版 // if (!reversed) animator.addListener(animatorListener1); return animator; } //定义动画状态监听器_跳转动画开启版 private Animator.AnimatorListener animatorListener1 = new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { // content.setVisibility(View.VISIBLE);//跳转进来时,(因为finish之前会将之设置为不可见,) // 根布局要设置为可见,与finish部分的不可见相对应 // mPuppet.setAlpha(1); } @Override public void onAnimationEnd(Animator animation) { mPuppet.startAnimation(createAlphaAnimation()); mPuppet.setVisibility(View.INVISIBLE);//动画结束时,揭露动画设置为不可见 } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }; private AlphaAnimation createAlphaAnimation() { AlphaAnimation aa = new AlphaAnimation(1,0); aa.setDuration(400); aa.setInterpolator(new AccelerateDecelerateInterpolator());//设置插值器 return aa; } } next.xml: <?xml version="1.0" encoding="utf-8"?> <FrameLayout 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:id="@+id/reveal_content" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.lwp.justtest.next"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Here is Second Activity!" android:textColor="@color/colorPrimaryDark" android:textSize="30sp" android:layout_gravity="center"/> <View android:id="@+id/view_puppet" android:background="@color/colorPrimaryDark" android:layout_width="match_parent" android:layout_height="match_parent"/> </FrameLayout> res/values/styles.xml: <resources> <!-- Base application theme. --> <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <!-- Customize your theme here. --> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> </style> <style name="noAnimTheme" parent="AppTheme"> <item name="android:windowAnimationStyle">@null</item> </style> </resources> AndroidManifest.xml: <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity" android:theme="@style/noAnimTheme"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".next" android:theme="@style/noAnimTheme"> </activity> </application> 最终效果图: 本文的两个demo就到此为止了,我相信以这个两个demo为模板,结合笔者之前关于Material Design做的诸多笔记,应该是可以做出不少很有趣的东西来的~
day 9 labor with one‘s minds 带着脑子干活;动脑子 it's not for you 不是适合你 do sb. service 为某人服务 do sb.a favor 帮助某人 eg:And it is nothing, nothing in the world 简直一无是处. Unless you can find joy in their intent to do you service.除非您体恤他们为您服务的一片诚心,能从中找到乐趣。 tender v.呈献,呈交; = to give ,present, offer adj.纤弱的; 嫩的; 温柔的; 疼痛的 For never anything can be amiss 双重否定,never否定,amiss“错误的”,表示“没有什么是错误的”,即“总是可取的” when simpleness and duty tender ti 淳朴(simpleness)和责任心所呈献(的礼物) For never anything can be amiss when simpleness and duty tender ti. 淳朴(simpleness)和责任心所呈献的(礼物)总是可取的。 I've visited good men who ...with... 这里I've visited表示“我所到达的地方”,后面接good men(有学问的人)怎么样怎么样 not pay me any welcome “一句欢迎的话也没有” I picked a welcome 领受到欢迎的诚意 bold 英 [bəʊld] 美 [boʊld] adj.明显的,醒目的; 勇敢的,无畏的; 莽撞的; 陡峭的 n.粗体字; 黑体字; boastful 英 [ˈbəʊstfl] 美 [ˈboʊstfl] adj.<贬>(人)好自夸的,(言辞)自吹自擂的; 夸诞; 虚夸; tongue 英 [tʌŋ] 美 [tʌŋ] n.舌头,喙; 语言,方言; 演说,鞋舌; [复]口才 eloquence 英 ['eləkwəns] 美 [ˈɛləkwəns] n.口才,辩才; 雄辩; 雄辩术; 修辞法 a busy tongue of bold and boastful eloquence 能言善辩之人的口才 tell over 反复讲述 body forth 体现、使具体化 make sense 有道理、有意义 break off 中断、突然停止 in conclusion 总之、最后 find out 发现 take pity on 同情、怜悯 labor with brain 动脑子 in fewest words 不多的话语 fierce beast 凶猛的野兽 That cruelly separate the two of we. cruelly.残酷的 day 10 tear down vt.向······猛扑,冲下······; 拆毁,拆卸; they tear down the wall that had separated them Now the hungry lion roars, And the wolf howls at the moon While the tired plowman snores, Grateful that his task is done. Now it is the time of night That the graves all open wide, Every one lets out its ghost In the churchyard paths to glide. And we fairies, that do run From the presence of the sun Come out and play. Now, not a mouse Shall disturb(vt.打扰) this blesséd house. I am sent with broom before, To sweep the dust behind the door. 饿狮在高声咆哮,豺狼在向月长嗥(howl at the moon);农夫们(plowman)鼾息沉沉,感恩一天劳作的完成。现在夜已经深深,坟墓都裂开大口(open wide);吐出了(let out)百千幽灵,墓园路上四散奔走(glide)。我们从太阳跑出,与大家一场欢闹;现在,驱走扰人的小鼠,吾奉命携帚,来揩干净(wipe the dust)门户。 amiss 英 [əˈmɪs] 美 [əˈmɪs] adj.出了差错的; 有毛病的; 有缺陷的; (用于否定句)不恰当的 adv.不恰当地; 不顺当地; 错误地; (可能因误会)对…生气 dreadful 英 [ˈdredfl] 美 [ˈdrɛdfəl] adj.可怕的; 令人畏惧的; 讨厌的,糟透了的,丑陋的; 糟糕的 n.<英>惊险小说[杂志] day 11 我没有做错任何事情(nor have I done anything else wrong) finish (.sb) off 英 [ˈfiniʃ ɔf] 美 [ˈfɪnɪʃ ɔf] 释义结束(某事); <非正>杀死; 毁掉; 吃光(某物)
这个报错比较骚,完全只是因为Intent没有写好而已,下面是错误的写法(最好不要在方法外赋值): 进入本活动时即刻报错: 11-10 18:23:24.231 17152-17152/? E/AndroidRuntime: FATAL EXCEPTION: main Process: com.lwp.justtest, PID: 17152 java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{com.lwp.justtest/com.lwp.justtest.next}: java.lang.NullPointerException: Attempt to invoke virtual method 'int android.content.Intent.getIntExtra(java.lang.String, int)' on a null object reference at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2548) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2707) at android.app.ActivityThread.-wrap12(ActivityThread.java) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1460) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:154) at android.app.ActivityThread.main(ActivityThread.java:6077) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:756) Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'int android.content.Intent.getIntExtra(java.lang.String, int)' on a null object reference at com.lwp.justtest.next.<init>(next.java:17) at java.lang.Class.newInstance(Native Method) at android.app.Instrumentation.newActivity(Instrumentation.java:1078) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2538) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2707) at android.app.ActivityThread.-wrap12(ActivityThread.java) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1460) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:154) at android.app.ActivityThread.main(ActivityThread.java:6077) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:756) 改成下面这样就可以跑了,哪里要用,哪里赋值:
实践内容: 1、运用 urdf建模 实现案例中的机器人。 2、根据以上掌握的方法,再快速创建一个机器人模型。 成果图: 成果图 创建需要用到的功能包以及各种文件夹: Build跟devel两个文件夹在catkin_make之后会自动生成: 编写launch文件: 添加左轮: 编译: 启动: roslaunch mbot_description display_mbot_base_urdf.launch 效果: (当然打开之后需要进行下面一番骚操作先——设置好RobotModel) 拖动滑动条控制轮子转动: 添加右轮: 编译,启动, 效果如下: 使用球体创建前后支撑轮: 编译,启动,效果如下: 创建一个自己的机器人模型: 代码: <?xml version="1.0"?> <robot name="mbot"> <link name="base_link"> <visual> <origin xyz="0 0 0" rpy="0 0 0"/> <geometry> <cylinder length="0.06" radius="0.20"/> </geometry> <material name="yellow"> <color rgba="1 0.4 0 1"/> </material> </visual> </link> <joint name="left_wheel_joint" type="continuous"> <origin xyz="0 0.19 -0.05" rpy="0 0 0"/> <parent link="base_link"/> <child link="left_wheel_link"/> <axis xyz="0 1 0"/> </joint> <link name="left_wheel_link"> <visual> <origin xyz="0 0 0" rpy="1.5707 0 0" /> <geometry> <cylinder radius="0.03" length = "0.025"/> </geometry> <material name="white"> <color rgba="1 1 1 0.9"/> </material> </visual> </link> <joint name="right_wheel_joint" type="continuous"> <origin xyz="0 -0.19 -0.05" rpy="0 0 0"/> <parent link="base_link"/> <child link="right_wheel_link"/> <axis xyz="0 1 0"/> </joint> <link name="right_wheel_link"> <visual> <origin xyz="0 0 0" rpy="1.5707 0 0" /> <geometry> <cylinder radius="0.03" length = "0.025"/> </geometry> <material name="white"> <color rgba="1 1 1 0.9"/> </material> </visual> </link> <joint name="front_caster_joint" type="continuous"> <origin xyz="0.18 0 -0.045" rpy="0 0 0"/> <parent link="base_link"/> <child link="front_caster_link"/> <axis xyz="0 1 0"/> </joint> <link name="front_caster_link"> <visual> <origin xyz="0 0 0" rpy="0 0 0" /> <geometry> <sphere radius="0.03" /> </geometry> <material name="black"> <color rgba="0 0 0 0.95"/> </material> </visual> </link> <joint name="back_caster_joint" type="continuous"> <origin xyz="-0.18 0 -0.045" rpy="0 0 0"/> <parent link="base_link"/> <child link="back_caster_link"/> <axis xyz="0 1 0"/> </joint> <link name="back_caster_link"> <visual> <origin xyz="0 0 0" rpy="0 0 0" /> <geometry> <sphere radius="0.03" /> </geometry> <material name="black"> <color rgba="0 0 0 0.95"/> </material> </visual> </link> </robot> 效果:
Debug完成图: Debug完成图 昨天晚上开始学一下这个揭露动画,准备用在项目中做一个转场,啃完了API之后开始写个小demo,距离跑成功一步之遥的当儿,出了个bug,就怎么点击按钮都没用。 首先上bug图: bug:怎么点击按钮都没用,每点击一次都会出现下面的报错(2040): 11-07 19:20:49.665 2454-2454/? I/Finsky: [1] com.google.android.finsky.services.j.a(149): Installation state replication succeeded. 11-07 19:20:49.711 7448-7448/? E/hei: 2040 11-07 19:20:49.718 1350-1371/? W/audio_hw_generic: Not supplying enough data to HAL, expected position 5613338 , only wrote 5613120 11-07 19:20:50.376 7448-7448/? E/hei: 2040 11-07 19:20:50.992 7448-7448/? E/hei: 2040 11-07 19:20:51.591 7448-7448/? E/hei: 2040 11-07 19:20:54.790 1350-1372/? W/audio_hw_generic: Not supplying enough data to HAL, expected position 6098164 , only wrote 5856480 11-07 19:20:58.322 7448-7448/? E/hei: 2040 11-07 19:20:58.328 1350-1371/? W/audio_hw_generic: Not supplying enough data to HAL, expected position 5856667 , only wrote 5856480 11-07 19:21:01.540 1350-1372/? W/audio_hw_generic: Not supplying enough data to HAL, expected position 6162708 , only wrote 6010560 activity.java全文: package com.lwp.justtest; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.support.design.widget.FloatingActionButton; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.view.ViewAnimationUtils; public class MainActivity extends AppCompatActivity implements View.OnClickListener { boolean flag = false; FloatingActionButton fab; private View mPuppet; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mPuppet = findViewById(R.id.view_puppet); fab = (FloatingActionButton)findViewById(R.id.fab); fab.setOnClickListener(this); } @Override public void onClick(View v) { doRevealAnimation(); } private void doRevealAnimation() { int[] vLocation = new int[2]; fab.getLocationInWindow(vLocation); int centerX = vLocation[0] + fab.getMeasuredWidth() / 2; int centerY = vLocation[1] + fab.getMeasuredHeight() / 2; int height = mPuppet.getHeight(); int width = mPuppet.getWidth(); int maxRradius = (int) Math.hypot(height, width); Log.e("hei", maxRradius + ""); if (flag) { Animator animator = ViewAnimationUtils.createCircularReveal(mPuppet, centerX, centerY, maxRradius, 0); animator.setDuration(1000); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); mPuppet.setVisibility(View.GONE); } }); animator.start(); flag = false; } else { Animator animator = ViewAnimationUtils.createCircularReveal(mPuppet, centerX, centerY, 0, maxRradius); animator.setDuration(1000); animator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { mPuppet.setVisibility(View.VISIBLE); } @Override public void onAnimationEnd(Animator animation) { } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); animator.start(); flag = true; } } } layout.xml全文: <?xml version="1.0" encoding="utf-8"?> <FrameLayout 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.lwp.justtest.MainActivity"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" android:layout_gravity="center" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <View android:id="@+id/view_puppet" android:layout_width="match_parent" android:layout_height="match_parent"/> <android.support.design.widget.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" android:layout_margin="16dp" android:backgroundTint="@color/colorPrimary" android:src="@drawable/map" app:pressedTranslationZ="10dp" /> </FrameLayout> 观众朋友们,这个看出毛病了吗。 我想了一下,额,会不会我的View没有设置颜色啊。。好的试了一下,果然是,可以说是很鬼畜了。。 layout.xml里边的View控件改成下面这样子,再次运行程序就成了(发现2040还是会报错,但是动画算是完美跑出来了,所以小伙伴们这里记得设置android:background以及android:visibility噢): <View android:id="@+id/view_puppet" android:background="@color/colorPrimaryDark" android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="gone"/> 效果图如下:
APP中需要实现LayoutInflater布局加载器动态加载布局,然而开启程序一运行就闪退。。。 FATAL EXCEPTION: main Process: com.lwp.justtest, PID: 6054 java.lang.RuntimeException: Unable to start activity ComponentInfo{com.lwp.justtest/com.lwp.justtest.MainActivity}: android.view.InflateException: Binary XML file line #19: Attempt to invoke virtual method 'boolean java.lang.String.equals(java.lang.Object)' on a null object reference at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2646) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2707) at android.app.ActivityThread.-wrap12(ActivityThread.java) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1460) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:154) at android.app.ActivityThread.main(ActivityThread.java:6077) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:756) Caused by: android.view.InflateException: Binary XML file line #19: Attempt to invoke virtual method 'boolean java.lang.String.equals(java.lang.Object)' on a null object reference Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.String.equals(java.lang.Object)' on a null object reference at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:761) at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:727) at android.view.LayoutInflater.rInflate(LayoutInflater.java:858) at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:821) at android.view.LayoutInflater.inflate(LayoutInflater.java:518) at android.view.LayoutInflater.inflate(LayoutInflater.java:426) at android.view.LayoutInflater.inflate(LayoutInflater.java:377) at android.support.v7.app.AppCompatDelegateImplV9.setContentView(AppCompatDelegateImplV9.java:292) at android.support.v7.app.AppCompatActivity.setContentView(AppCompatActivity.java:140) at com.lwp.justtest.MainActivity.onCreate(MainActivity.java:21) at android.app.Activity.performCreate(Activity.java:6662) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1118) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2599) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2707) at android.app.ActivityThread.-wrap12(ActivityThread.java) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1460) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:154) at android.app.ActivityThread.main(ActivityThread.java:6077) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:756) 于是上CSDN扒博文(参考:https://blog.csdn.net/qq_36408196/article/details/79968169),结果发现,这个坑相当有毒。。。 错误示范。。。 问题就在控件View的书写,View的首字母要大写,要大写,大写,写。。 呐,改成上图这样即可了。
刚装完的VS2015,打开网站之后右键项目添加新项时,没有web窗体,SQL Server数据库等选项,如: 解决方法:工具—>扩展和更新: 更新安装ASP.NET 工具(下图两个都装): 更新完了双击安装: 安装完就没毛病了:
最近在搞ASP.NET,因实验室VS版本跟PC不一样可能,拷回来一打开就这样子: 眉头一皱的我打开我的古董SQL,自从用了MySQL就没碰它了我的锅。。。果然。。连接的时候。。。 不慌,(win 10)打开控制面板\系统和安全\管理工具 ->服务, 找到SQL Server(MSSQLSEVER),右键,启动或重启动: 读条,完了之后看状态,正在运行咯: 一顿骚(cainiao)操作之后,再次打开sql,完美解决了,连接成功: 接着VS点击连接到数据库: 接下来有两种方法: 方法一(推荐,简单粗暴):直接在弹出来的窗口中选择好路径后点击确定: 然后会提示如下,继续点击确定即可: 搞定: 方法二(仅供参考): 完了点击确定即可。 附记:补充更新安装ASP.NET 工具:
首先,给已经装好的Ubuntu 16.04换上映像文件: 在安装VMware Tools时,需要修改指向VMware Tools所在(VMware Workstation\linux.iso),在这个路径下有个linux.iso文件,其中提供了linux操作系统平台需要的一些工具文件,当然包括VMware Tools安装文件。 现在再进入系统,在VMware菜单栏找到如下红框位置,我这里安装好了所以显示重新安装,未安装的时候可以这里开始第一步, 点击之后会弹出如下虚拟驱动,里面就有VMware Tools的安装包(即 * .tar.gz)(下图截自https://blog.csdn.net/sh21_/article/details/52453493)。 我们直接将这个压缩包拷到桌面上: 接着打开终端解压之(-xzvf 后面的路径可以直接把压缩文件拖到终端窗口上然后放开,修整一下路径即可): tar -xzvf /home/.../VMwareTools-10.0.6-。。。.tar.gz 解压时终端剪影: 解压完毕后,cd进入解压后的目录,执行:sudo ./wmware-install.pl 然后基本上一直回车就可以了(看到yes或者no的都输入yes(如果你英语自觉不错那也可以自行阅读询问后操作),其他的基本上回车就可以了,会输默认INPUT: [...] default)。 下面是我安装的剪影: liweipeng@liweipeng-virtual-machine:~$ cd vmware-tools-distrib/ liweipeng@liweipeng-virtual-machine:~/vmware-tools-distrib$ sudo ./vmware-install.pl [sudo] liweipeng 的密码: open-vm-tools packages are available from the OS vendor and VMware recommends using open-vm-tools packages. See http://kb.vmware.com/kb/2073803 for more information. Do you still want to proceed with this installation? [no] yes INPUT: [yes] Creating a new VMware Tools installer database using the tar4 format. Installing VMware Tools. In which directory do you want to install the binary files? [/usr/bin] INPUT: [/usr/bin] default What is the directory that contains the init directories (rc0.d/ to rc6.d/)? [/etc] INPUT: [/etc] default ...... Before running VMware Tools for the first time, you need to configure it by invoking the following command: "/usr/bin/vmware-config-tools.pl". Do you want this program to invoke the command for you now? [yes] yes INPUT: [yes] Initializing... Making sure services for VMware Tools are stopped. Stopping Thinprint services in the virtual machine: Stopping Virtual Printing daemon: done Stopping VMware Tools services in the virtual machine: Guest operating system daemon: done VMware User Agent (vmware-user): done Unmounting HGFS shares: done Guest filesystem driver: done The module vmci has already been installed on this system by another installer or package and will not be modified by this installer. The module vsock has already been installed on this system by another installer or package and will not be modified by this installer. The module vmxnet3 has already been installed on this system by another installer or package and will not be modified by this installer. The module pvscsi has already been installed on this system by another installer or package and will not be modified by this installer. The module vmmemctl has already been installed on this system by another installer or package and will not be modified by this installer. The VMware Host-Guest Filesystem allows for shared folders between the host OS and the guest OS in a Fusion or Workstation virtual environment. Do you wish to enable this feature? [yes] yes INPUT: [yes] The vmxnet driver is no longer supported on kernels 3.3 and greater. Please upgrade to a newer virtual NIC. (e.g., vmxnet3 or e1000e) The vmblock enables dragging or copying files between host and guest in a Fusion or Workstation virtual environment. Do you wish to enable this feature? [yes] yes INPUT: [yes] VMware automatic kernel modules enables automatic building and installation of VMware kernel modules at boot that are not already present. This feature can be enabled/disabled by re-running vmware-config-tools.pl. Would you like to enable VMware automatic kernel modules? [yes] yes INPUT: [yes] Thinprint provides driver-free printing. Do you wish to enable this feature? [yes] yes INPUT: [yes] Disabling timer-based audio scheduling in pulseaudio. Do you want to enable Guest Authentication (vgauth)? Enabling vgauth is needed if you want to enable Common Agent (caf). [yes] yes INPUT: [yes] Do you want to enable Common Agent (caf)? [no] yes INPUT: [yes] Detected X server version 1.19.6 Distribution provided drivers for Xorg X server are used. Skipping X configuration because X drivers are not included. ...... Enjoy, --the VMware team Found VMware Tools CDROM mounted at /media/liweipeng/VMware Tools1. Ejecting device /dev/sr0 ... 直到出现下面这块就算安装成功了: 接着关机: 回到VMware的界面,点击虚拟机->设置->选项, 对共享文件夹添加一个本机路径即可,文件夹可以用来存放共享文件: 接着在Ubuntu中任意点击一个文件(文件夹可以先压缩成.tar.gz文件),Ctrl+C: 然后到主机随便找个地方,比如共享文件夹,Ctrl+V,就哦了,整个儿直接地便从虚拟机复制到主机上来,相当好吃: 参考博文:https://www.linuxidc.com/Linux/2016-04/130806.htmhttps://www.linuxidc.com/Linux/2016-04/130807.htmhttps://blog.csdn.net/sh21_/article/details/52453493
蝉声陪伴着行云流浪回忆开始后安静遥望远方“诶,同学,刚刚是你吹的笛子吗”“额,嗯,是,怎么了”“好听~吹得我快掉眼泪了。。。”......(来自10.13,收摊前,一个挺有感触的瞬间) 那一刻再次体会到, 乐器这种东西,一拿起来, 别人就有可能透过那抑扬之声, 看到你内心一角幻化而出的世界吧。。 匆匆忙忙,行程紧凑爆肝的一周, 如梦似幻结束了 补坑:周一到周五 计网代理服务器录屏, 肝ROS、总线、操作系统作业 肝项目文档, 肝Android文档, 百团大战:周一三五六 从地库到篮球场到排球场 周末: 同乡会聚餐 老友生日 敬老院重阳敬老 移动联盟分享会 不甘社分享会 秋风起了, 长风不绝呢
在CSDN找到了“博客搬家”的功能,接下了要进行博客搬家了,应官方要求,发表本文章。
Material Design Material Design 实战 之第四弹 —— 卡片布局以及灵动的标题栏(CardView & AppBarLayout) Material Design 实战 之第五弹 —— 下拉刷新(SwipeRefreshLayout) Material Design 实战 之 第六弹 —— 可折叠式标题栏(CollapsingToolbarLayout) & 系统差异型的功能实现(充分利用系统状态栏空间)
本模块共有六篇文章,参考郭神的《第一行代码》,对Material Design的学习做一个详细的笔记,大家可以一起交流一下: Material Design 实战 之第一弹——Toolbar(即本文) Material Design 实战 之第二弹——滑动菜单详解&实战 Material Design 实战 之第三弹—— 悬浮按钮和可交互提示(FloatingActionButton & Snackbar & CoordinatorLayout) Material Design 实战 之第四弹 —— 卡片布局以及灵动的标题栏(CardView & AppBarLayout) Material Design 实战 之第五弹 —— 下拉刷新(SwipeRefreshLayout) Material Design 实战 之 第六弹 —— 可折叠式标题栏(CollapsingToolbarLayout) & 系统差异型的功能实现(充分利用系统状态栏空间) 引子: 文章提要与总结 1. CollapsingToolbarLayout 1.1 CollapsingToolbarLayout是一个作用于Toolbar基础之上的布局,由DesignSupport库提供。 1.2 CollapsingToolbarLayout不能独立存在, 它在设计的时候就被限定只能作为AppBarLayout的直接子布局来使用。 而AppBarLayout又必须是CoordinatorLayout的子布局; 1.3 水果详情界面布局框架: CoordinatorLayout下分三部分:水果标题栏、水果详情栏、悬浮按钮; 具体属性意义详见文章; <?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout ...... android:fitsSystemWindows="true"> <!--水果标题栏--> <android.support.design.widget.AppBarLayout ...... android:layout_height="250dp" android:fitsSystemWindows="true"> <android.support.design.widget.CollapsingToolbarLayout ...... android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" android:fitsSystemWindows="true" app:contentScrim="?attr/colorPrimary" app:layout_scrollFlags="scroll|exitUntilCollapsed"> <ImageView ...... android:scaleType="centerCrop" android:fitsSystemWindows="true" app:layout_collapseMode="parallax"/> <android.support.v7.widget.Toolbar ...... app:layout_collapseMode="pin"/> </android.support.design.widget.CollapsingToolbarLayout> </android.support.design.widget.AppBarLayout> <!--水果详情栏--> <android.support.v4.widget.NestedScrollView ...... app:layout_behavior="@string/appbar_scrolling_view_behavior"> <LinearLayout ......> <android.support.v7.widget.CardView ...... android:layout_marginBottom="15dp" ...... app:cardCornerRadius="4dp"> <TextView ...... android:layout_margin="10dp"/> </android.support.v7.widget.CardView> </LinearLayout> </android.support.v4.widget.NestedScrollView> <android.support.design.widget.FloatingActionButton ...... android:src="@drawable/ic_comment" app:layout_anchor="@id/appBar" app:layout_anchorGravity="bottom|end"/> </android.support.design.widget.CoordinatorLayout> 1.4 水果详情界面java逻辑: public class FruitActivity extends AppCompatActivity { 全局变量:设置约定 intent 传输键值常量; @Override protected void onCreate(Bundle savedInstanceState){ 此类是水果详情页,通过intent获得来自水果卡片列表页传来的数据(水果名字和图片id); 实例化诸对象; 设置toolbar 设置导航按钮 设置折叠栏标题 加载图片,设置文字 } 利用StringBuilder重复fruitname生成长字符串 private String generateFruitContent(String fruitName){ } 响应导航按钮 @Override public boolean onOptionsItemSelected(MenuItem item) {} } 1.5 处理RecyclerView的点击事件,将点击到的卡片子项提取出name和imageId, 用intent传给水果详情界面展示; 2. 充分利用系统状态栏空间(系统差异型) 2.1 将控件(这里是ImageView)布局结构中的所有父布局的 android:fitsSystemWindows属性指定成true,就表示该控件会出现在系统状态栏里; 2.2 在程序的主题中将状态栏颜色指定成透明色; 在主题中将android:statusBarColor属性的值指定成@android:color/transparent; 2.3 创建一个values-v21目录;values-v21目录下创建一个styles.xml文件; 编写: <?xml version="1.0" encoding="utf-8"?> <resources> <style name="FruitActivityTheme" parent="AppTheme"> <item name="android:statusBarColor">@android:color/transparent</item> </style> </resources> 2.4 修改values/styles.xml文件: <resources>...... <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> ...... </style> <style name="FruitActivityTheme" parent="AppTheme"> </style> </resources> 2.5 修改AndroidManifest.xmI: <activity android:name=".FruitActivity" android:theme="@style/FruitActivityTheme"> </activity> 效果图 正文 可折叠式标题栏(CollapsingToolbarLayout) 顾名思义,CollapsingToolbarLayout是一个作用于Toolbar基础之上的布局,由DesignSupport库提供。 CollapsingToolbarLayout可以让Toolbar的效果变得更加丰富,不仅仅是展示一个标题栏,而是能够实现非常华丽的效果。 不过CollapsingToolbarLayout不能独立存在,它在设计的时候就被限定只能作为AppBarLayout的直接子布局来使用。而AppBarLayout又必须是CoordinatorLayout的子布局。 本文来做一个额外的活动作为水果的详情展示界面,当点击水果列表卡片的时候就进入这个界面。 右击com.example.materialtest包—>New—>Activity—>EmptyActivity,创建一个FruitActivity,并将布局名指定成activity_fruit.xml。 接着我们来编写这个布局。 Activity_fruit.xml中的内容主要分为两部分,一个是水果标题栏,一个是水果内容详情。 首先实现标题栏部分,这里使用CoordinatorLayout来作为最外层布局(我们在讲监测snackbar弹出,解决其遮挡悬浮按钮问题的时候用到过这个布局),如下: <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> </android.support.design.widget.CoordinatorLayout> 接着在里面嵌套一个AppBarLayout: <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.design.widget.AppBarLayout android:id="@+id/appBar" android:layout_width="match_parent" android:layout_height="250dp"> </android.support.design.widget.AppBarLayout> </android.support.design.widget.CoordinatorLayout> 这里主要是将高度指定为250dp,郭神亲测觉得这样视觉效果比较好。 接着在AppBarLayout中再嵌套一个CollapsingToolbarLayout: <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.design.widget.AppBarLayout android:id="@+id/appBar" android:layout_width="match_parent" android:layout_height="250dp"> <android.support.design.widget.CollapsingToolbarLayout android:id="@+id/collapsing_toolbar" android:layout_width="match_parent" android:layout_height="match_parent" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" app:contentScrim="?attr/colorPrimary" app:layout_scrollFlags="scroll|exitUntilCollapsed"> </android.support.design.widget.CollapsingToolbarLayout> </android.support.design.widget.AppBarLayout> </android.support.design.widget.CoordinatorLayout> 这里使用了新的布局CollapsingToolbarLayout。 其中, android:theme指定了ThemeOverlay.AppCompat.Dark.ActionBar主题,之前(第四弹)在activitymain.xml中给Toolbar指定的也是这个主题,只不过这里要实现更加高级的Toolbar效果,因此需要将这个主题的指定提到上一层来。 app:contentScrim指定CollapsmgToolbarLayout在趋于折叠状态以及折叠之后的背景色, 其实CollapsingToolbarLayout在折叠之后就是一个普通的Toolbar,背景色是colorPrimary; app:layout_scrollFlags之前(第四弹)是给Toolbar指定的,现在也移到外面来了。 其中, scroll表示CollapsingToolbarLayout会随着水果内容详情的滚动一起滚动, exitUntilCollapsed表示当CollapsingToolbarLayout随着滚动完成折叠之后就保留在界面上,不再移出屏幕。 接下来在CollapsingToolbarLayout中定义标题栏的具体内容: ...... <android.support.design.widget.CollapsingToolbarLayout android:id="@+id/collapsing_toolbar" android:layout_width="match_parent" android:layout_height="match_parent" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" app:contentScrim="?attr/colorPrimary" app:layout_scrollFlags="scroll|exitUntilCollapsed"> <ImageView android:id="@+id/fruit_image_view" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" app:layout_collapseMode="parallax"/> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:layout_collapseMode="pin"/> </android.support.design.widget.CollapsingToolbarLayout> ...... 这里在CollapsingToolbarLayout中定义了一个ImageView和一个Toolbar,也即这个高级版的标题栏是由普通的标题栏加上图片组合而成的。 以及, app:layout_collapseMode用于指定当前控件在CollapsingToolbarLayout折叠过程中的折叠模式, 其中Toolbar指定成pin,表示在折叠的过程中位置始终保持不变, ImageView指定成parallax,表示会在折叠的过程中产生一定的错位偏移,这种模式的视觉效果会非常好。 下面编写水果内容详情部分,继续改activity_fruit.xml: ...... </android.support.design.widget.CollapsingToolbarLayout> </android.support.design.widget.AppBarLayout> <android.support.v4.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"> </android.support.v4.widget.NestedScrollView> </android.support.design.widget.CoordinatorLayout> 我们知道ScrollView允许使用滚动的方式来查看屏幕以外的数据, 而NestedScrollView在此基础之上增加了嵌套响应滚动事件的功能。 由于CoordinatorLayout本身已经可以响应滚动事件了, 因此我们在它的内部就需要使用NestedScrollView或RecyclerView这样可以响应滚动事件的布局。 另外,通过 app:layout_behavior属性指定一个布局行为,这和之前 第四弹 在RecyclerView中的用法是一模一样的。 不管是ScrollView还是NestedScroIIView,它们的内部都只允许存在一个直接子布局。 如果我们想要在里面放入很多东西的话,通常都会先嵌套一个LinearLayout,然后再在LinearLayout中放入具体的内容,如下: ...... <android.support.v4.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content"> </LinearLayout> </android.support.v4.widget.NestedScrollView> </android.support.design.widget.CoordinatorLayout> 接下来在LinearLayout中放入具体的内容, 使用一个TextView来显示水果的内容详情, 并将TextView放在一个卡片式布局当中: ...... </android.support.design.widget.AppBarLayout> <android.support.v4.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content"> <android.support.v7.widget.CardView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="15dp" android:layout_marginLeft="15dp" android:layout_marginRight="15dp" android:layout_marginTop="35dp" app:cardCornerRadius="4dp"> <TextView android:id="@+id/fruit_content_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="10dp"/> </android.support.v7.widget.CardView> </LinearLayout> </android.support.v4.widget.NestedScrollView> </android.support.design.widget.CoordinatorLayout> 这里主要要注意的是,为了让界面更加美观,在CardView和TextView上都加了一些边距。 其中,CardView的marginTop加了35dp的边距,这是为下面要编写的东西留出空间。 至此水果标题栏和水果内容详情的界面便编写完了。 接着还可以在界面上再添加一个悬浮按钮, 当然并不是必需的,只是如果加的话,我们将免费获得一些额外的动画效果。 这里就实战一下,在activity_fruit.xml中加一个关于水果的表示评论作用的悬浮按钮。 首先需要提前准备好一个图标, 这里放置了一张ic_comment.png到drawable-xxhdpi目录下。 然后修改activity_fruit.xml: ...... </android.support.v4.widget.NestedScrollView> <android.support.design.widget.FloatingActionButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="16dp" android:src="@drawable/ic_comment" app:layout_anchor="@id/appBar" app:layout_anchorGravity="bottom|end"/> </android.support.design.widget.CoordinatorLayout> 这里加了一个FloatingActionButton,它和AppBarLayout(水果标题栏)以及NestedScrollView(水果详情栏)布局平级。 FloatingActionButton中,app:layou_anchor属性(anchor n.锚状物)指定了一个锚点,这里将锚点设置为AppBarLayout,这样悬浮按钮就会出现在水果标题栏的区域内;app:layout_anchorGravity属性将悬浮按钮定位在标题栏区域的右下角。 至此activity_fruit.xml布局(水果详情界面)便写完了。 界面完成了之后,接着开始编写功能逻辑,修改FruitActivity(水果详情界面的逻辑): public class FruitActivity extends AppCompatActivity { //设置约定 intent 传输键值常量 public static final String FRUIT_NAME = "fruit_name"; public static final String FRUIT_IMAGE_ID = "fruit_image_id"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_fruit); //此类是水果详情页,通过intent获得来自水果卡片列表页传来的数据(水果名字和图片id) Intent intent = getIntent(); String fruitname = intent.getStringExtra(FRUIT_NAME); int fruitImageId = intent.getIntExtra(FRUIT_IMAGE_ID, 0); //实例化诸对象 Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar); CollapsingToolbarLayout collapsingToolbar = (CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar); ImageView fruitImageView = (ImageView) findViewById(R.id.fruit_image_view); TextView fruitContentText = (TextView) findViewById(R.id.fruit_content_text); //设置toolbar setSupportActionBar(toolbar); //设置导航按钮 ActionBar actionBar = getSupportActionBar(); if(actionBar != null){ actionBar.setDisplayHomeAsUpEnabled(true); } //设置折叠栏标题 collapsingToolbar.setTitle(fruitname); //加载图片,设置文字 Glide.with(this).load(fruitImageId).into(fruitImageView); String fruitContent = generateFruitContent(fruitname); fruitContentText.setText(fruitContent); } //利用StringBuilder重复fruitname生成长字符串 private String generateFruitContent(String fruitName){ StringBuilder fruitContent = new StringBuilder(); for(int i = 0; i < 500; i++){ fruitContent.append(fruitName); } return fruitContent.toString(); } //响应导航按钮 @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()){ case android.R.id.home: finish(); return true; } return super.onOptionsItemSelected(item); } } 接下来处理RecyclerView的点击事件,将点击到的卡片子项提取出name和imageId, 用intent传给水果详情界面展示,下面修改FruitAdapter: ...... //加载子布局,将子项作为参数传给ViewHolder,在ViewHolder里面 //为cardView添加点击事件 @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if(mContext == null){ mContext = parent.getContext(); } View view = LayoutInflater.from(mContext).inflate(R.layout.fruit_item, parent, false); final ViewHolder holder = new ViewHolder(view);//将子项作为参数传给ViewHolder,在ViewHolder里面面实例化子项中的各个对象 holder.cardView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { int position = holder.getAdapterPosition();//获得列表对应子项的位置 //mFruitList由new适配器的时候传进来的构造函数的参数提供, // get(position)将子项对应位置的水果对象从设置到适配器中的水果列表数据中取出来 Fruit fruit = mFruitList.get(position); Intent intent = new Intent(mContext, FruitActivity.class); intent.putExtra(FruitActivity.FRUIT_NAME, fruit.getName()); intent.putExtra(FruitActivity.FRUIT_IMAGE_ID,fruit.getImageId()); mContext.startActivity(intent); } }); return holder; } ...... 修改前(原全文见第四弹): ...... //加载子布局,将子项作为参数传给ViewHolder,在ViewHolder里面 @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if(mContext == null){ mContext = parent.getContext(); } View view = LayoutInflater.from(mContext).inflate(R.layout.fruit_item, parent, false); return new ViewHolder(view);//将子项作为参数传给ViewHolder,在ViewHolder里面实例化子项中的各个对象 } ...... 重新运行一下程序,效果如下: 向上拖动水果图片,背景图上的标题会慢慢缩小,而且会产生错位偏移的效果,toolbar的位置和图片还会产生透明度的变化等等,效果十分炫酷。 这是由于用户想要查看水果的内容详情,此时界面的重点在具体的内容上面,因此标题栏就会自动进行折叠,从而节省屏幕空间。 继续向上拖动,直到标题栏变成完全折叠状态,效果如图: 而这个时候向下拖动水果内容详情,就会执行一个完全相反的动画过程。最终恢复刚刚点进来的样子。 那个这里的话其实有个小尴尬,再次强调注意命名规范的重要性了。。。首先这个是水果详情界面标题栏的ImageView,id是fruit_image_view: 另下面这个是卡片水果列表界面的ImageView,id是fruit_image: 我在水果详情界面的逻辑中,findViewbyid写错成了卡片水果列表界面的ImageView的id: 以至于点击水果卡片的时候报错: java.lang.IllegalArgumentException: You must pass in a non null View 毕竟点击水果卡片之后是要跳转到水果详情界面了,逻辑还要去设置进入onStop()状态的卡片水果列表界面的控件,显然这肯定是不行的。 将刚刚写错的地方——实例化使用的id改正一下即可: ImageView fruitImageView = (ImageView) findViewById(R.id.fruit_image_view); 充分利用系统状态栏空间 这里如果将背景图和状态栏融合到一起,绝对能让视觉体验提升好几个档次了。 只不过Android5.0系统之前是无法对状态栏的背景或颜色进行操作的,那个时候也没有Matenal Design的概念。 而Android5.0及之后的系统就支持这个功能。 所以这里需要一个系统差异型的效果,即 对于Android5.0及之后的系统使用背景图和状态栏融合的模式; 在之前的系统中使用普通的模式; 让背景图和系统状态栏融合,需要借助Android:fitsSystemWindows这个属性来实现。 在 CoordinatorLayout(外层监听框架)、 AppBarLayout(水果详情界面标题栏外层)、 CollapsingToolbarLayout(水果详情界面标题栏)这种嵌套结构的布局中, 将控件的android:fitsSystemWindows属性指定成true,就表示该控件会出现在系统状态栏里。 对应到我们的程序,那就是水果标题栏中的ImageView应该设置这个属性了。 不过只给ImageView设置这个属性是没有用的, 我们必须将ImageView布局结构中的所有父布局都设置上这个属性才可以, 修改activity_fruit.xml中的代码,如下所示: 这里除了将android:fitsSystemWindows属性设置好,还必须在程序的主题中将状态栏颜色指定成透明色。 方法很简单,在主题中将android:statusBarColor属性的值指定成@android:color/transparent即可。 但android:statusBarCoIor这个属性是从API 21,即Android5.0系统开始才有的,之前的系统无法指定这个属性。 那么,系统差异型的功能实现至此开始; 右击res目录—>New—>Directory,创建一个values-v21目录,然后右击values-v21目录—>New —>Values resource file,创建一个styles.xml文件。对这个文件进行编写: <?xml version="1.0" encoding="utf-8"?> <resources> <style name="FruitActivityTheme" parent="AppTheme"> <item name="android:statusBarColor">@android:color/transparent</item> </style> </resources> 这里定义了一个FruitActivityTheme主题, 它是专门给FruitAcuvity使用的。 FruitActivityTheme的parent主题是AppTheme,也就是说它继承了AppTheme中的所有特性。 然后在FruitAcuvityTheme中将状态栏的颜色指定成透明色, 由于values-v21目录是只有Android5.0及以上的系统才会去读取的, 因此这么声明是没有问题的。 但是Android5.0之前的系统却无法识别FruitActivityTheme这个主题,因此还需修改values/styles.xml文件: <resources> <!-- Base application theme. --> <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <!-- Customize your theme here. --> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> </style> <!--修改了这儿--> <style name="FruitActivityTheme" parent="AppTheme"> </style> </resources> 这里也定义了一个FruitActivityTheme主题,并且parent主题也是AppTheme,但是它的内部是空的。 因为Android5.0之前的系统无法指定状态栏的颜色,因此这里什么都不用做就可以了。 5.0之前的版本会加载这里的FruitActivityTheme,也就是间接地使用了默认的AppTheme主题; 5.0之后的版本或许也加载这里的FruitActivityTheme,但同时读取values-v21的styles,随后刚刚我们做的设置状态栏的代码会将这里的覆盖掉,也就是使用了我们编写的新的FruitActivityTheme。 于是达到了系统差异型的功能实现的目的。 最后还需让FruitActivity使用这个主题,修改AndroidManifest.xmI: <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.materialtest"> <application ...... <activity android:name=".FruitActivity" android:theme="@style/FruitActivityTheme"> </activity> </application> </manifest> 这里使用android:theme属性单独给FruitActivity指定了这个主题,到这里就大功告成了。 现在只要是在Android5.0及以上的系统运行这个MaterialTest程序,水果详情展示界面的效果便如下: 跟刚刚的效果相比,视觉体验是完全不同档次的。 关于的 Material Design 学习到此就告一段落了,具体的可以参考 Material Design的官方文档:
本模块共有六篇文章,参考郭神的《第一行代码》,对Material Design的学习做一个详细的笔记,大家可以一起交流一下: Material Design 实战 之第一弹——Toolbar(即本文) Material Design 实战 之第二弹——滑动菜单详解&实战 Material Design 实战 之第三弹—— 悬浮按钮和可交互提示(FloatingActionButton & Snackbar & CoordinatorLayout) Material Design 实战 之第四弹 —— 卡片布局以及灵动的标题栏(CardView & AppBarLayout) Material Design 实战 之第五弹 —— 下拉刷新(SwipeRefreshLayout) Material Design 实战 之 第六弹 —— 可折叠式标题栏(CollapsingToolbarLayout) & 系统差异型的功能实现(充分利用系统状态栏空间) 引子: 文章提要与总结 SwipeRefreshLayout 1.SwipeRefreshLayout即是实现下拉刷新功能的核心类,它由support-v4库提供的; 2.把想要实现下拉刷新功能的控件放置到SwipeRefreshLayout里边,即可迅速让这个控件支持下拉刷新了; 3.接下来在对应的java代码中处理具体的刷新逻辑: 3.1 实例化SwipeRefreshLayout; 3.2 调用setcolorSchemeResources()方法来设置下拉刷新进度条的颜色; 3.3 调用setonRefreshListener()方法设置一个下拉刷新的监听器, 传入一个SwipeRefreshLayout.OnRefreshListener()并重写onRefresh()来处理具体的刷新逻辑; 3.4 刷新逻辑使用中可以使用如下多线程结构: new Thread(new Runnable() { @Override public void run() { try{ }catch (InterruptedException e){ e.printStackTrace(); } runOnUiThread(new Runnable() { @Override public void run() { } }); } }).start(); 其中try中书写耗时操作,然后在 runOnUiThread() 中的 run() 中获取到数据, 并adapter.notifyDataSetChanged()调用刷新数据; 最后调用swipeRefreshLayout的setRefreshing()并传入false,表示刷新事件结束同时隐藏刷新进度条; 正文 SwipeRefreshLayout (英 [swaɪp]) SwipeRefreshLayout即是实现下拉刷新功能的核心类,它由support-v4库提供的。 把想要实现下拉刷新功能的控件放置到SwipeRefreshLayout里边,即可迅速让这个控件支持下拉刷新了。 而在这里的实战项目(MaterialTest)中,应该支持下拉刷新功能的控件是RecyclerView。 下面直接开始使用它。修改activity-main.xml: <?xml version="1.0" encoding="utf-8"?> <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.design.widget.CoordinatorLayout android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.design.widget.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" app:popupTheme="@style/ThemeOverlay.AppCompat.Light" app:layout_scrollFlags="scroll|enterAlways|snap"/> </android.support.design.widget.AppBarLayout> <android.support.v4.widget.SwipeRefreshLayout android:id="@+id/swipe_refresh" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior = "@string/appbar_scrolling_view_behavior"> <android.support.v7.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" /> </android.support.v4.widget.SwipeRefreshLayout> <android.support.design.widget.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" android:layout_margin="16dp" android:src="@drawable/ic_done" app:elevation="8dp"/> </android.support.design.widget.CoordinatorLayout> <android.support.design.widget.NavigationView android:id="@+id/nav_view" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="start" app:menu="@menu/nav_menu" app:headerLayout="@layout/nav_header"> </android.support.design.widget.NavigationView> </android.support.v4.widget.DrawerLayout> 这里在RecyclerView的外面再嵌套一层SwipeRefreshLayout,让RecyclerView实现下拉刷新功能。 另注意, 由于RecyclerView现在变成了Swipe-RefreshLayout的子控件, 因此之前使用app:layout_behavxor声明的布局行为现在也要移到SwipeRefreshLayout中才行。 当然,虽RecyclerView已经支持下拉刷新功能,但还要在代码中处理具体的刷新逻辑才行。 下面修改MainActivity: //下拉刷新 private SwipeRefreshLayout swipeRefresh; ------------------------------------ //下拉刷新逻辑处理 swipeRefresh = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh); swipeRefresh.setColorSchemeResources(R.color.colorPrimary); swipeRefresh.setOnRefreshListener(new SwipeRefreshLayout. OnRefreshListener(){ @Override public void onRefresh() { refreshFruits(); } }); ------------------------------------ private void refreshFruits(){ new Thread(new Runnable() { @Override public void run() { try{ Thread.sleep(2000); }catch (InterruptedException e){ e.printStackTrace(); } runOnUiThread(new Runnable() { @Override public void run() { initFruits(); adapter.notifyDataSetChanged(); swipeRefresh.setRefreshing(false); } }); } }).start(); } 这里, 首先实例化SwipeRefreshLayout, 然后调用setcolorSchemeResources()方法来设置下拉刷新进度条的颜色,这里使用主题中的colorPrimary作为进度条的颜色。 接着调用setonRefreshListener()方法设置一个下拉刷新的监听器,当触发了下拉刷新操作的时候就会回调这个监听器的onRefresh()方法,在这个方法中处理具体的刷新逻辑。(这里可以类比setOnClickListener理解) 通常onRefresh()方法中应该是去网络上请求最新的数据,然后再将这些数据展示出来。 这里就不和网络交互了,简单地写一个refreshFruits()方法并调用它进行本地刷新操作。 refreshFruits()方法中先是开启了一个线程,然后将线程沉睡两秒钟,模拟刷新的等待过程。 因为本地刷新操作速度非常快,如果不将线程沉睡的话,刷新会即刻结束而看不到刷新的过程。 沉睡结束后使用run0nUiThread()方法将线程切换回主线程, 调用initFruits()方法重新生成数据, 接着调用FruitAdapter的notifyDataSetChanged()通知数据发生了变化并刷新adapter里面的数据, 最后调用swipeRefreshLayout的setRefreshing()并传入false,表示刷新事件结束同时隐藏刷新进度条。 重新运行一下程序,在屏幕的主界面向下拖动,会出现下拉刷新的进度条,松手后就会自动进行刷新了,效果如图: 刷新中 刷新后 下拉刷新进度条会停留两秒钟,随后自动消失,水果列表也会更新了。
本模块共有六篇文章,参考郭神的《第一行代码》,对Material Design的学习做一个详细的笔记,大家可以一起交流一下: Material Design 实战 之第一弹——Toolbar(即本文) Material Design 实战 之第二弹——滑动菜单详解&实战 Material Design 实战 之第三弹—— 悬浮按钮和可交互提示(FloatingActionButton & Snackbar & CoordinatorLayout) Material Design 实战 之第四弹 —— 卡片布局以及灵动的标题栏(CardView & AppBarLayout) Material Design 实战 之第五弹 —— 下拉刷新(SwipeRefreshLayout) Material Design 实战 之 第六弹 —— 可折叠式标题栏(CollapsingToolbarLayout) & 系统差异型的功能实现(充分利用系统状态栏空间) 卡片式布局也是MaterialsDesign中提出的一个新的概念,它可以让页面中的元素看起来就像在卡片中一样,并且还能拥有圆角和投影,下面我们就开始具体学习一下。 最终成果图 文章提要与总结 1. CardView(这里用于作为recycleview的子项,用于显示水果) 1.1 实际上,CardView也是一个FrameLayout,只是额外提供了圆角和阴影等效果,看上去会有立体的感觉; 1.2 app:cardCornerRadius属性指定卡片圆角的弧度,数值越大,圆角的弧度也越大; app:elevation属性指定卡片的高度, 高度值越大,投影范围也越大,但是投影效果越淡, 高度值越小,投影范围也越小,但是投影效果越浓, FloatingActionButton同理。 1.3 需要依赖: compile 'com.android.support:cardview-v7:25.3.1' 本项目还需添加一个Glide库的依赖。 compile 'com.github.bumptech.glide:glide:3.7.0' Glide是一个超级强大的图片加载库,它不仅可以用于加载本地图片, 还可以加载网络图片、GIF图片、甚至是本地视频。 最重要的是,Glide的用法非常简单,只需一行代码就能轻松实现复杂的图片加载功能; 1.4 在toolbar下面添加一个recycleview 定义一个实体类Fruit,方便后面存取数据; 为RecycleView的子项制定一个自定义布局(架构如下): <android.support.v7.widget.CardView <LinearLayout <ImageView/> <TextView/> </LinearLayout> </android.support.v7.widget.CardView> 接下来需要为RecyclerView准备一个适配器, 适配器中除了RecycleView的设计逻辑之外,这里需要注意的是, 在onBindViewHoIder()方法中使用Glide来加载水果图片。 Glide的用法: 首先调用Glide.with()方法并传入一个Context、Activity或Fragment参数; 然后调用load()方法去加载图片,其参数可以是一个URL地址 或 本地路径 或 资源id; 最后调用into()方法将图片设置到具体某一个ImageView中即可。 1.5 在MainActivity中: 初始化水果列表; 实例化recyclerView ; newLayoutManager & set; new & set adapter; 2.AppBarLayout 2.1 将Toolbar嵌套到AppBarLayout中; 2.2 给RecyclerView指定一个布局行为(app:layout_behavior)——appbar_scrolling_view_behavior 2.3 在Toolbar中添加一个app:layout_scrollFlags属性,并其值指定成了scroll|enterAlways|snap。 其中, scroll 表示当RecyclerView向上滚动时,Toolbar会跟着一起向上滚动并实现隐藏; enterAlways 表示当RecyclerView向下滚动时,Toolbar会跟着一起向下滚动并重新显示; snap 表示当Toolbar还没有完全隐藏或显示时,会根据当前滚动的距离,自动选择是隐藏还是显示。 效果图 正文 CardView 首先这里准备用CardView来填充主题内容, CardView是用于实现卡片式布局效果的重要控件,由appcompat-v7库提供。 实际上,CardView也是一个FrameLayout,只是额外提供了圆角和阴影等效果,看上去会有立体的感觉。 CardView 的基本用法: <android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="5dp" app:cardCornerRadius="4dp"> <TextView android:id="@+id/fruit_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_margin="5dp" android:textSize="16sp"/> </android.support.v7.widget.CardView> 其中: app:cardCornerRadius属性指定卡片圆角的弧度,数值越大,圆角的弧度也越大; app:elevation属性指定卡片的高度, 高度值越大,投影范围也越大,但是投影效果越淡, 高度值越小,投影范围也越小,但是投影效果越浓, FloatingActionButton同理。 然后我们在CardView布局中放置了一个TextView,这个TextView就会显示在一张卡片中了。 为充分利用屏幕的空间,我们可以使用RecyclerView来填充MatenalTest项目的主界面部分。 这里参考一下郭神的demo——实现水果列表,首先需要准备许多张水果图片: 然后在app/build.gradle文件中声明RecyclerView、CardView这几个控件对应的库的依赖: compile 'com.android.support:recyclerview-v7:25.3.1' compile 'com.android.support:cardview-v7:25.3.1' 注意这里还添加了一个Glide库的依赖。compile 'com.github.bumptech.glide:glide:3.7.0' Glide是一个超级强大的图片加载库,它不仅可以用于加载本地图片,还可以加载网络图片、GIF图片、甚至是本地视频。最重要的是,Glide的用法非常简单,只需一行代码就能轻松实现复杂的图片加载功能,因此这里我 们准备用它来加载水果图片。 Glide的项目主页地址是:https://github.com/bumptech/glide。 接下来修改activity-main.xml,如下所示(在toolbar下面添加一个recycleview), <?xml version="1.0" encoding="utf-8"?> <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.design.widget.CoordinatorLayout android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/> <android.support.v7.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent"/> <android.support.design.widget.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" android:layout_margin="16dp" android:src="@drawable/ic_done" app:elevation="8dp"/> </android.support.design.widget.CoordinatorLayout> <android.support.design.widget.NavigationView android:id="@+id/nav_view" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="start" app:menu="@menu/nav_menu" app:headerLayout="@layout/nav_header"> </android.support.design.widget.NavigationView> </android.support.v4.widget.DrawerLayout> 接着定义一个实体类Fruit,方便后面存取数据: public class Fruit { private String name; private int imageId; public Fruit(String name, int imageId){ this.name = name; this.imageId = imageId; } public String getName(){ return name; } public int getImageId() { return imageId; } } 类中就两个字段, name对应水果的名字; imageId对应图片的资源id。 接下来需要为RecycleView的子项制定一个自定义布局。在layout目录下新建fruit_item.xml: <?xml version="1.0" encoding="utf-8"?> <android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="5dp" app:cardCornerRadius="4dp"> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content"> <ImageView android:id="@+id/fruit_image" android:layout_width="match_parent" android:layout_height="100dp" android:scaleType="centerCrop"/> <TextView android:id="@+id/fruit_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_margin="5dp" android:textSize="16sp"/> </LinearLayout> </android.support.v7.widget.CardView> 这里使用了CardView来作为子项的最外层布局,从而使得RecyclerView中的每个元素都是在卡片当中的。 CardView由于是一个FrameLayout,因此它没有什么方便的定位方式,这里只好在CardView中再嵌套一个LinearLayout,然后在LinearLayout中放置具体的内容。 内容的话就是 定义了ImageView用于显示水果的图片, 定义了TextView用于显示水果的名称,并让TextView在水平方向上居中显示。 注意在ImageView中我们使用了一个scaleType属性,这个属性可以指定图片的缩放模式。 由于各张水果图片的长宽比例可能都不一致,为了让所有的图片都能填充满整个ImageView,这里使用了centerCrop模式,它可以让图片保持原有比例填充满ImageView,并将超出屏幕的部分裁剪掉。 接下来需要为RecyclerView准备一个适配器, 新建FruitAdapter类继承RecyclerView.Adapter,并将泛型指定为FruitAdapter.ViewHolder,代码如下(具体可见代码中注释): public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> { private Context mContext; private List<Fruit> mFruitList; //实例化子项布局各个view对象 static class ViewHolder extends RecyclerView.ViewHolder{ CardView cardView; ImageView fruitImage; TextView fruitName; public ViewHolder(View view){ super(view); cardView = (CardView) view; fruitImage = (ImageView) view.findViewById(R.id.fruit_image); fruitName = (TextView) view.findViewById(R.id.fruit_name); } } public FruitAdapter(List<Fruit> fruitList){ mFruitList = fruitList; } //加载子布局,将子项作为参数传给ViewHolder,在ViewHolder里面 @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if(mContext == null){ mContext = parent.getContext(); } View view = LayoutInflater.from(mContext).inflate(R.layout.fruit_item, parent, false); return new ViewHolder(view);//将子项作为参数传给ViewHolder,在ViewHolder里面面实例化子项中的各个对象 } //set对应子项对象 @Override public void onBindViewHolder(ViewHolder holder, int position) { Fruit fruit = mFruitList.get(position);//get对应子项对象 holder.fruitName.setText(fruit.getName()); Glide.with(mContext).load(fruit.getImageId()).into(holder.fruitImage); } @Override public int getItemCount() { return mFruitList.size(); } } 除了RecycleView的设计逻辑之外,这里需要注意的是,在onBindViewHoIder()方法中使用Glide来加载水果图片。 Glide的用法: 首先调用Glide.with()方法并传入一个Context、Activity或Fragment参数; 然后调用load()方法去加载图片,其参数可以是一个URL地址/本地路径/资源id; 最后调用into()方法将图片设置到具体某一个ImageView中即可。 这里使用Glide而不是传统的设置图片方式: 因这里从网上找的这些水果图片像素都非常高,如果不进行压缩直接展示,很容易就会引起内存溢出。 而使用Glide就完全不需要担心这回事,因为Glide在内部做了许多非常复杂的逻辑操作, 其中就包括了图片压缩,只需要安心按照Glide的标准用法去加载图片就可以了。 这样RecyclerView的适配器便准备好了,最后修改MainActivity中的代码: public class MainActivity extends AppCompatActivity { private DrawerLayout mDrawerLayout; //增加RecycleView后的数据和对象初始化 private Fruit[] fruits = {new Fruit("Apple", R.drawable.apple),new Fruit("Banana", R.drawable.banana), new Fruit("Orange", R.drawable.orange),new Fruit("Watermelon", R.drawable.watermelon), new Fruit("Pear", R.drawable.pear),new Fruit("Grape", R.drawable.grape), new Fruit("Pineapple", R.drawable.pineapple),new Fruit("Strawberry", R.drawable.strawberry), new Fruit("Cherry", R.drawable.cherry),new Fruit("Mango", R.drawable.mango)}; private List<Fruit> fruitList = new ArrayList<>(); private FruitAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); //滑动菜单 & 导航按钮 mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); NavigationView navView = (NavigationView) findViewById(R.id.nav_view); ActionBar actionBar = getSupportActionBar(); if(actionBar != null){ actionBar.setDisplayHomeAsUpEnabled(true);//让导航按钮显示出来 actionBar.setHomeAsUpIndicator(R.drawable.ic_menu);//设置一个导航按钮图标 } //滑动菜单布局交互设置 navView.setCheckedItem(R.id.nav_call);//将Call菜单项设置为默认选中 navView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener(){ @Override public boolean onNavigationItemSelected(@NonNull MenuItem item) { mDrawerLayout.closeDrawers();//关闭滑动菜单 return true; } }); //悬浮按钮点击事件 FloatingActionButton fab = (FloatingActionButton)findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // Toast.makeText(MainActivity.this, "FAB clickes", Toast.LENGTH_SHORT).show(); //Snackbar Snackbar.make(v,"Data deleted", Snackbar.LENGTH_SHORT) .setAction("Undo", new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(MainActivity.this, "Data restored", Toast.LENGTH_SHORT).show(); } }).show(); } }); initFruits(); //实例化 RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view); //newLayoutManager & set GridLayoutManager layoutManager = new GridLayoutManager(this, 2); recyclerView.setLayoutManager(layoutManager); //new & set adapter adapter = new FruitAdapter(fruitList); recyclerView.setAdapter(adapter); } //初始化水果列表 private void initFruits(){ fruitList.clear(); for (int i = 0; i < 50; i++){ Random random = new Random(); int index = random.nextInt(fruits.length);//nextInt()作用:产生[0,fruits.length)之间的int数 fruitList.add(fruits[index]); } } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.toolbar,menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()){ case android.R.id.home: mDrawerLayout.openDrawer(GravityCompat.START); break; case R.id.backup: Toast.makeText(this,"You clicked Backup" , Toast.LENGTH_SHORT).show(); break; case R.id.delete: Toast.makeText(this,"You clicked Delete" , Toast.LENGTH_SHORT).show(); break; case R.id.settings: Toast.makeText(this,"You clicked Settings" , Toast.LENGTH_SHORT).show(); break; default: } return true; } } 代码简析: 在MainActivity中定义了一个数组,数组存放多个Fruit的实例,每个实例代表一种水果; 在initFruits()方法中,先清空fruitList中的数据,再使用一个随机函数,从刚才定义的Fruit数组中随机挑选一个水果放入到fruitList当中,这样每次打开程序看到的水果数据都会是不同的。 另外,为了让界面上的数据多一些,这里使用了一个循环,随机挑选50个水果。 之后是RecyclerView的逻辑,这里使用GridLayoutManager布局方式。 GridLayoutManager的构造函数接收两个参数,第一个是Context,第二个是列数,这里指定为2,表示每一行中会有两列数据。 运行效果如图: 可见Toolbar被挡住了,不急,接下来学习另外一个工具——AppBarLayout,完美解决这个问题。 AppBarLayout 首先RecyclerView会把Toolbar给遮挡住的原因: 由于RecyclerView和Toolbar都是放置在CoordinatorLayout中的, 而前面已经说过,CoordinatorLayout就是一个加强版的FrameLayout, 而FrameLayout中的所有控件在不进行明确定位的情况下,默认都会摆放在布局的左上角,从而也就产生了遮挡的现象。解决方法: 传统情况下,使用偏移是唯一的解决办法, 即让RecyclerView向下偏移一个Toolbar的高度,从而保证不会遮挡到Toolbar。 不过这里使用的是DesignSupport库的CoordinatorLayout而不是FrameLayout,自然会有更加巧妙的解决办法。 这里准备使用DesignSupport库中提供的另外一个工具——AppBarLayout。 AppBarLayout实际上是一个垂直方向的LinearLayout,它在内部做了很多滚动事件的封装,并应用了一MaterialDesign的设计理念。 接下来使用AppBarLayout两步解决前面的覆盖问题: 第一步将Toolbar嵌套到AppBarLayout中, 第二步给RecyclerView指定一个布局行为(app:layout_behavior)。 修改activity_main.xml: <?xml version="1.0" encoding="utf-8"?> <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.design.widget.CoordinatorLayout android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.design.widget.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/> </android.support.design.widget.AppBarLayout> <android.support.v7.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"/> <android.support.design.widget.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" android:layout_margin="16dp" android:src="@drawable/ic_done" app:elevation="8dp"/> </android.support.design.widget.CoordinatorLayout> <android.support.design.widget.NavigationView android:id="@+id/nav_view" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="start" app:menu="@menu/nav_menu" app:headerLayout="@layout/nav_header"> </android.support.design.widget.NavigationView> </android.support.v4.widget.DrawerLayout> 改动后布局文件并没有太大变化: 首先定义一个AppBarLayout,并将Toolbar放置在AppBarLayout里面; 然后在RecyclerView中使用app:layout_behavior属性指定一个布局行为。 其中appbar_scrolling_view_behavior这个字符串也是由DesignSupport库提供的。 重新运行一下程序,可见遮挡问题就此解决了: 至此AppBarLayout已成功解决RecyclerView遮挡Toolbar的问题,但是这里还并没有体现AppBarLayout中应用的MaterialDesign设计理念, 其实,当RecyclerView滚动的时候就便将滚动事件都通知给AppBarLayout了 (记得刚刚加的app:layout_behavior="@string/appbar_scrolling_view_behavior"吗,看一下这个字符串,顾名思义应该可以看出些端倪,这里可以先抽象理解为这个属性指定了的便是RecyclerView滚动的时候做出的行为), 只是上面的代码还没进行处理而已。 当AppBarLayout接收到滚动事件的时候,它内部的子控件是可以指定如何去影响这些事件的, 通过app:layout_scrollFlags属性就能实现。 下面进一步优化,加一个代码看看AppBarLayout的这个Material Design效果,修改activity-main.xml: app:layout_scrollFlags="scroll|enterAlways|snap" 这里在Toolbar中添加一个app:layout_scrollFlags属性,并其值指定成了scroll|enterAlways|snap。 其中, scroll表示当RecyclerView向上滚动时,Toolbar会跟着一起向上滚动并实现隐藏; enterAlways表示当RecyclerView向下滚动时,Toolbar会跟着一起向下滚动并重新显示; snap表示当Toolbar还没有完全隐藏或显示时,会根据当前滚动的距离,自动选择是隐藏还是显示。 这里要改动的其实也就这一行代码而已,重新运行一下程序,并向上滚动RecyclerView,效果如图: 运行程序可见, 随着我们 向上滚动RecyclerView会Toolbar消失掉; 向下滚动RecyclerView,Toolbar又会重新出现; 滚动到Toolbar的一半时松开手指,Toolbar又会根据当前滚动的距离情况,做出消失或者重新出现的反应; 这其实也是MaterialDesign中的一项重要设计思想,因为当用户在向上滚动RecyclerView的时候,其注意力肯定是在RecyclerView的内容上面的,这个时候如果Toolbar还占据着屏幕空间,就会在一定程度上影响用户的阅读体验,而将Toolbar隐藏则可以让阅读体验达到最佳状态。当用户需要操作Toolbar上的功能时,只需要轻微向下滚动,Toolbar就会重新出现。 这种设计方式,既保证了用户的最佳阅读效果,又不影响任何功能上的操作,Material Design考虑得就是这么细致人微。 当然了,像这种功能,如果是使用ActionBar的话,那就完全不可能实现了,TooIbar的出现为我们提供了更多的可能。
上标题图 曾几何时,还是游吟长安的醉客,日携竹笛,夜饮星辉,随风潇尔。。。 国庆节放假 ~ 话说在简书撰技术博客良久了,抽个空另谈别趣。 曾经游迹古风此间, 今时若同学有兴交流,自当还是棋乐茶酒相迎。 大一时分,仍持一学期拈一长文之习, 字句斟酌,游笔墨纸,心中所想,借诗而道,也算畅快, 以下方为大一下所笔拙作一篇,投文学社公众号, 若有兴趣,或可一观。 也即《临末三十日之世界》。点击前往 ~ 另,投蜀江文学,今时道是,历两年风雨,再观当时文风,觉来还方青涩稚气,若有大佬临观,还望不笑话: 好梦 品世五歌(《三行情书》、《三行心言》、《暇时凌风散吟》等) 现专研技术了,收不下心去看多些文字、写些长文, 或许留心的朋友可看到笔者简书专题栏的图用的都古风,算念念过往尔尔。 现在,也即偶尔茶足饭饱,步路课室,会稍稍放空,捏造些那短句,发作朋友圈,微言意气, 以下方是最近一则: 秋风散吟 果秋风一起,人总是善感些的 ~至此
图片来自百度 于珠海。秋风乍起,温侯宜人,落木不时。。 尝世间 历谓沧桑 待深秋万山红遍 修一笔长青 矗百仞绝峰 望九州川海 四方霄云 沐雨凌霜鏖飓 不问明月 亦不问星辰
当时一开始装VS2015,是用了很长时间了。装了之后是可以用的,打了不少代码的。 后来很长一段时间没用它了,时隔良久,双击快捷方式竟然打开不了,让我“重装”。 其实也不用重装,本机用的Win10,去控制面板找到VS,双击它,进入“安装程序”,有修改/修复/卸载三个按钮,点击修复就可以了(嗯窗口类似长这样): 修复的时间没有安装用的时间那么长,是快了很多的,大概就半个小时多一点, 装完之后便可以打开了,不会出现像之前那样的报错(“文件损坏,需要重装”)。 但是这一开始打开,出现了别的坑——未能正确加载“...(各种)Package”包。也是个毛病,便百度,搜到了类似这样的解决办法, 找到像这样的目录 C:\Users\poloy32\AppData\Local\Microsoft\VisualStudio\14.0\ComponentModelCache 删除目录下的这个文件即可解决问题: Microsoft.VisualStudio.Default.cache 接下来,是另外一个坑——VS一打开就无反应状态, 这个真的无解,百度看了很多方法,都没用, 但直觉是重启可以解决的,于是乎配合资源管理器的强制关闭以多次各种重启VS,最后果然是没有问题了, 没有用到论坛上的任一方法,简简单单地配合资源管理器各种重启,最后是没毛病地打开,正常运行。
本文记录安装一个第三方仿真软件包的实战,从下载到成功运行! 环境:ubuntu16.04 & ROS-Ubuntu2 最终效果图 下载源码包 在Ubuntu系统上,确保git已经安装 sudo apt-get install git 然后再创建一个名为tutorial_ws的工作空间,在它的src路径下克隆ROS-Academy-for-Beginners软件包 cd mkdir -p tutorial_ws/src 创建catkin工作空间 cd tutorial_ws/src 进入src路径,克隆教学软件包 git clone https://github.com/DroidAITech/ROS-Academy-for-Beginners.git 安装依赖 安装依赖: cd ~/tutorial_ws rosdep install --from-paths src --ignore-src --rosdistro=kinetic -y 过程图: 注意:以上命令非常重要,缺少依赖将导致软件包无法正常编译和运行。 在开始编译之前,需要确保Gazebo在7.0版本以上 gazebo -V 编译 接着回到catkin_ws下编译: cd ~/tutorial_ws catkin_make 过程图: 下面是刷新环境的两种方法: source ~/tutorial_ws/devel/setup.bash rospack profile 过程图: 运行仿真程序 首先需要解决一个问题,不然待会儿运行时会闪退: 虚拟机上无法运行Gazebo的问题?然后进入工作空间,输入下方代码即可: roslaunch robot_sim_demo robot_spawn.launch 下面是运行过程图(是的,一开始可能会遇到一个报错[robot_spawn.launch] is neither a launch file in package [robot_sim_demo] nor is [robot_sim_demo] a launch file name The traceback for the exception was written to the log file,然而下面的过程图已经是从报错到解决报错成功运行的过程了): liweipeng@liweipeng-virtual-machine:~$ rospack profile Full tree crawl took 0.189714 seconds. Directories marked with (*) contain no manifest. You may want to delete these directories. To get just of list of directories without manifests, re-run the profile with --zombie-only ------------------------------------------------------------- 0.141810 /opt/ros/kinetic/share 0.000770 * /opt/ros/kinetic/share/OpenCV-3.3.1-dev 0.000257 * /opt/ros/kinetic/share/doc 0.000043 * /opt/ros/kinetic/share/OpenCV-3.3.1-dev/haarcascades 0.000019 * /opt/ros/kinetic/share/OpenCV-3.3.1-dev/lbpcascades 0.000008 * /opt/ros/kinetic/share/doc/liborocos-kdl liweipeng@liweipeng-virtual-machine:~$ roslaunch robot_sim_demo robot_spawn.launch [robot_spawn.launch] is neither a launch file in package [robot_sim_demo] nor is [robot_sim_demo] a launch file name The traceback for the exception was written to the log file liweipeng@liweipeng-virtual-machine:~$ roslaunch robot_sim_demo robot_spawn.launch [robot_spawn.launch] is neither a launch file in package [robot_sim_demo] nor is [robot_sim_demo] a launch file name The traceback for the exception was written to the log file liweipeng@liweipeng-virtual-machine:~$ cd ~/tutorial_ws liweipeng@liweipeng-virtual-machine:~/tutorial_ws$ roslaunch robot_sim_demo robot_spawn.launch [robot_spawn.launch] is neither a launch file in package [robot_sim_demo] nor is [robot_sim_demo] a launch file name The traceback for the exception was written to the log file liweipeng@liweipeng-virtual-machine:~/tutorial_ws$ source ~/tutorial_ws/devel/setup.bash liweipeng@liweipeng-virtual-machine:~/tutorial_ws$ rospack profile Full tree crawl took 0.025369 seconds. Directories marked with (*) contain no manifest. You may want to delete these directories. To get just of list of directories without manifests, re-run the profile with --zombie-only ------------------------------------------------------------- 0.016329 /opt/ros/kinetic/share 0.008478 /home/liweipeng/tutorial_ws/src 0.008405 /home/liweipeng/tutorial_ws/src/ROS-Academy-for-Beginners 0.000169 * /opt/ros/kinetic/share/OpenCV-3.3.1-dev 0.000036 * /opt/ros/kinetic/share/doc 0.000035 * /opt/ros/kinetic/share/OpenCV-3.3.1-dev/haarcascades 0.000015 * /opt/ros/kinetic/share/OpenCV-3.3.1-dev/lbpcascades 0.000006 * /opt/ros/kinetic/share/doc/liborocos-kdl liweipeng@liweipeng-virtual-machine:~/tutorial_ws$ roslaunch robot_sim_demo robot_spawn.launch ... logging to /home/liweipeng/.ros/log/3bcd5ac4-be9d-11e8-a270-000c29f43d2c/roslaunch-liweipeng-virtual-machine-2603.log Checking log directory for disk usage. This may take awhile. Press Ctrl-C to interrupt Done checking log file disk usage. Usage is <1GB. xacro: Traditional processing is deprecated. Switch to --inorder processing! To check for compatibility of your document, use option --check-order. For more infos, see http://wiki.ros.org/xacro#Processing_Order xacro.py is deprecated; please use xacro instead started roslaunch server http://liweipeng-virtual-machine:41819/ SUMMARY ======== PARAMETERS 再打开一个终端,输入以下命令,用键盘控制机器人移动,聚焦控制程序窗口,按下i、j、l等按键,控制机器人移动: rosrun robot_sim_demo robot_keyboard_teleop.py 过程图如下,记得运行前要刷新环境,或者用上面的方法把刷新命令添加到脚本: 呐,对比上图,我把它移到了简介牌的右边:
Android Material Design 实战 之第一弹——Toolbar详解 Material Design 实战 之第二弹——滑动菜单详解&实战(DrawerLayout & NavigationView) Material Design 实战 之第三弹—— 悬浮按钮和可交互提示(FloatingActionButton & Snackbar & CoordinatorLayout) 本地模拟服务器开发与交互——Apache服务器填坑之路(下载、安装、使用demo、卸载) AVD Nexus_5X_API_24 is already running. If that is not the case, delete the files at C:....a... 课业笔记 ROS机器人程序设计 机器人程序设计_ROS_note1 机器人程序设计_ROS_note2 VMware Workstation14.1.3 & Ubuntu18.04从安装到实用的填坑之路 ROS安装全过程(十分壮观,是个大坑来的) 硬件编程 Keil uVision4起步简单编程 __note1 RFID课程前置——SQL巩固练习
本文主要是贴图,截图简述安装过程,安装了一个晚上,刷各种bug,翻山越岭,身心疲惫,是个令人难忘的历程! 本文安装参考的官方文档 完成1.1-1.3的内容 看一下滚动条跟时间,相当的壮观。 运行了一次sudo apt-get install ros-kinetic-desktop,第二次运行: 运行了一次sudo apt-get install ros-kinetic-desktop-full,第二次运行: 第三次运行sudo apt-get install ros-kinetic-desktop-full: 运行sudo apt-get install ros-kinetic-slam-gmapping: 至此完成1.4的内容: 完成1.5 完成1.6 完成1.7 好了,操作到这里基本就完事了,现在可以开始跑机器人程序设计_ROS_note1里面的小乌龟的例子了。
“最大的感慨就是当年Ubuntu15.10的时代,装个中文输入法翻山越岭的,然而Ubuntu18.04怎么样?直接默认安装好了!还有Ubuntu15.10现在想上装点东西就各种毛病,要这要那的,Ubuntu18.04呢?流畅丝滑噼里啪啦嘎嘣脆。”果断更新VMware Workstation14.1.3 & Ubuntu18.04吧~ 本文主要解决了VM14.1.3的安装问题; 以及安装过程中VM跟Ubuntu的两个报错解决方案 本机VMware Workstation环境是上学期安装的,拿的老师给的安装包,版本10.0.1。顺便装了个Ubuntu15.10。 搞了一暑假Android了,开学选上了大数据分析应用基础和机器人程序设计两门专业选修课,都要搞Linux,便搬出来了这套老古董。 今天想装个Ubuntu18.04,怎奈果然遇到了很多坑,这里记录下来,跟大家分享分享。 首先我是打算在VM10.0.1上面装Ubuntu18.04的,呵呵呵真天真: 呐,从官网下载完了镜像文件,一装,结果就是这个样子,人家不兼容的。。。 销魂的报错吼: end kernel panic not syncing:corrupted stack end detected inside scheduler 好吧,百度一下,大家都说要搞个VMware Workstation14.1.3才行,好的那就搞吧。 戳这里去官网下载 戳进去之后是这个界面: 记得点击红框内的文字,进入下面这个页面: 选择对应的版本就可以下载了。 下载完了即这个软件,双击打开之后选择好安装路径,一直下一步就好了,下面是过程的截图: 好了最棒的是VM14在安装的时候会自动帮你卸载掉旧的版本,看着桌面快捷方式的这个空洞,楼主深感欣慰: 好了很快就安装完毕了,双击打开即可: 然后还要你输入密钥。。。额好吧,你懂的: CG54H-D8D0H-H8DHY-C6X7X-N2KG6 ZY5H0-D3Y8K-M89EZ-AYPEG-MYUA8 FF31K-AHZD1-H8ETZ-8WWEZ-WUUVA CV7T2-6WY5Q-48EWP-ZXY7X-QGUWD 输入秘钥之后弹框:“感谢您...”,好的我会“使用愉快”的,不客气哈: 接着创建虚拟机,填写各种配置然后置入映像文件即可。 好的最后一步开启虚拟机,你还可能遇到这个错误: PXE-MOF:Exiting intel PXE ROM.Operating system not found 又是相当销魂的报错对吧。。 不急,就是置入映像文件的时候,忘记打了个勾。。 好了打勾之后重启ubuntu,大功告成: 下面便是令你心旷神怡神清气爽的安装过程的截影了,“使用愉快”!
本笔记关于ROS文件系统认识以及常用命令的实践 环境:ubuntu16.04 & ROS-Ubuntu2 0.安装tree: 1.创建目录,在目录上创建工作空间,使用tree查看: 2.编译工作空间: 3.此时再次使用tree查看,文件树已然大变:
今天开启模拟机,弹出来这么个毛病: AVD Nexus_5X_API_24 is already running. If that is not the case, delete the files at C:\...\.android\avd/Nexus_5X_API_24.avd/*.lock and try again. 查了网上博文,说是因为虚拟机刚搭建好的时候默认会建立"*.lock"文件夹,而且当你把虚拟机关掉时这个文件夹会自动删除,可能是上次关PC时太草率,所以这个逗比文件夹没给删除掉, 好了,那跟着提示去找那个文件夹(报错都没给全名字对吧,可好找了),将之删除,再启动模拟机,就OK了:
近期在上RFID技术课程,前期要求巩固SQL知识,课上做了下面几道练习题,由此记录一下 先把以下程序复制到新查询窗口中运行: create database EX30918 -- 创建表 create table T_CallRecords( id int not null, CallerNumber varchar(3), TellNumber varchar(13), StartDateTIme datetime, EndDateTime datetime, Primary key(Id) ); --插入数据 insert into T_CallRecords(Id,CallerNumber,TellNumber,StartDateTime,EndDateTIme) values(1,'001','02088888888','2010-7-10 10:01','2010-7-10 10:05'); INSERT INTO T_CallRecords(Id,CallerNumber,TellNumber,StartDateTime,EndDateTime) VALUES (2,'002','02088888888', '2010-7-11 13:41','2010-7-11 13:52'); INSERT INTO T_CallRecords(Id,CallerNumber,TellNumber,StartDateTime,EndDateTime) VALUES (3,'003','89898989', '2010-7-11 14:42', '2010-7-11 14:49'); INSERT INTO T_CallRecords(Id,CallerNumber,TellNumber,StartDateTime,EndDateTime) VALUES (4,'004','02188368981', '2010-7-13 21:04', '2010-7-13 21:18'); INSERT INTO T_CallRecords(Id,CallerNumber,TellNumber,StartDateTime,EndDateTime) VALUES (5,'005','76767676', '2010-6-29 20:15', '2010-6-29 20:30'); INSERT INTO T_CallRecords(Id,CallerNumber,TellNumber,StartDateTime,EndDateTime) VALUES (6,'006','02288878243', '2010-7-15 13:40', '2010-7-15 13:56'); INSERT INTO T_CallRecords(Id,CallerNumber,TellNumber,StartDateTime,EndDateTime) VALUES (7,'007','67254686', '2010-7-13 11:06', '2010-7-13 11:19'); INSERT INTO T_CallRecords(Id,CallerNumber,TellNumber,StartDateTime,EndDateTime) VALUES (8,'008','8623<st1:rtx w:st="on">1445</st1:rtx>', '2010-6-19 19:19', '2010-6-19 19:25'); INSERT INTO T_CallRecords(Id,CallerNumber,TellNumber,StartDateTime,EndDateTime) VALUES (9,'009','87422368', '2010-6-19 19:25', '2010-6-19 19:36'); INSERT INTO T_CallRecords(Id,CallerNumber,TellNumber,StartDateTime,EndDateTime) VALUES (10,'010','40045862245', '2010-6-19 19:50', '2010-6-19 19:59'); --修改呼叫员编号 UPDATE T_CallRecords SET CallerNumber='001' WHERE Id IN (1,2,3,6,9); UPDATE T_CallRecords SET CallerNumber='002' WHERE Id IN (4,5); UPDATE T_CallRecords SET CallerNumber='003' WHERE Id IN (7,8); UPDATE T_CallRecords SET CallerNumber='004' WHERE Id=10; --数据汇总 select * from T_CallRecords 显示的结果如下: 根椐上面的表完成以下题目: -- 1) 输出所有数据中通话时间最长的5条记录。 select top 5 *, DATEDIFF(second,StartDateTime,EndDateTime) as 'SpanTimex' from T_CallRecords order by 'SpanTimex' Desc -- 2) 输出所有数据中拨打长途号码(对方号码以0开头)的总时长。 --输出所有数据中拨打长途号码 select *,DATEDIFF(second,StartDateTIme,EndDateTime) as 'Total_SpanTimex' from T_CallRecords where TellNumber like '0%' -- 输出所有数据中拨打长途号码(对方号码以0开头)的总时长 select sum(DATEDIFF(second,StartDateTIme,EndDateTime)) as 'Total_SpanTimex' from T_CallRecords where TellNumber like '0%' -- 3) 输出在2010年7月通话总时长最多的前三个呼叫员的编号。 --主要思路: --算出2010-7到现在时间的月数month,数据中起始时间到现在时间等于month的,即2010年7月的记录 --sum(DATEDIFF(second,StartDateTIme,EndDateTime))算出通话总时长 select top 3 CallerNumber,sum(DATEDIFF(second,StartDateTIme,EndDateTime)) as 'Total_SpanTimex' from T_CallRecords where DATEDIFF(month,StartDateTIme,getdate())=(select DATEDIFF(month,'2010-7-1',getdate())) group by CallerNumber order by 'Total_SpanTimex' desc -- 4) 输出2010年7月拨打电话次数最多的前三个呼叫员的编号。 select top 3 CallerNumber, count(*) from T_CallRecords where DATEDIFF(month,StartDateTIme,getdate())=(select DATEDIFF(month,'2010-7-1',getdate())) group by CallerNumber -- 5) 输出所有数据的拨号流水,并且在最后一行添加总呼叫时长。 -- 记录呼叫员编号、对方号码、通话时长 --... -- 汇总[市内号码总时长][长途号码总时长] -- 5) 输出所有数据的拨号流水,并且在最后一行添加总呼叫时长。 -- 记录呼叫员编号、对方号码、通话时长 -- ...... -- 汇总[市内号码总时长][长途号码总时长] --取对应的列,转化类型; --因为两个表加在一起,列的属性要一样!!! select convert(nvarchar(50),CallerNumber) as N'呼叫员编码' , convert(nvarchar(50),TellNumber) as N'对方号码', convert(nvarchar(50),DATEDIFF(second,StartDateTIme,EndDateTime)) as N'通话时长' from T_CallRecords union all select '汇总', -- (select sum(DATEDIFF(second,StartDateTIme,EndDateTime)) from T_CallRecords where TellNumber not like '0%') 算出市内号码总时长 --convert(nvarchar(50), (select sum(...) from T_CallRecords where TellNumber not like '0%')) 转换类型方便转型 --select (select '市内号码总时长:' + convert(nvarchar(50), (select sum(DATEDIFF(second,StartDateTIme,EndDateTime)) from T_CallRecords where TellNumber not like '0%')) + '秒'), --注意逗号!!!,两个逗号连接三个列为一行 (select '长途号码总时长:' + convert(nvarchar(50), (select sum(DATEDIFF(second,StartDateTIme,EndDateTime)) from T_CallRecords where TellNumber like '0%')) + '秒')
本模块共有六篇文章,参考郭神的《第一行代码》,对Material Design的学习做一个详细的笔记,大家可以一起交流一下: Material Design 实战 之第一弹——Toolbar(即本文) Material Design 实战 之第二弹——滑动菜单详解&实战 Material Design 实战 之第三弹—— 悬浮按钮和可交互提示(FloatingActionButton & Snackbar & CoordinatorLayout) Material Design 实战 之第四弹 —— 卡片布局以及灵动的标题栏(CardView & AppBarLayout) Material Design 实战 之第五弹 —— 下拉刷新(SwipeRefreshLayout) Material Design 实战 之 第六弹 —— 可折叠式标题栏(CollapsingToolbarLayout) & 系统差异型的功能实现(充分利用系统状态栏空间) 文章提要与总结 1. DrawerLayout 控件用处:实现滑动菜单 1.1 首先它是一个布局,在布局中允许放入两个直接子控件, 第一个子控件是主屏幕中显示的内容; 第二个子控件是滑动菜单中显示的内容; 关于第二个子控件有一点需要注意,layout_gravity这个属性是必须指定的:left right start 1.2 添加导航按钮: 1.2.1 首先调用findViewById()方法得到了DrawerLayout的实例; 1.2.2 getSupportActionBar()方法得到了ActionBar的实例; 1.2.3 调用ActionBar的setDisplayHomeAsUpEnabled()让导航按钮显示出来; 1.2.4 调用了setHomeAsUpIndicator()方法来设置一个导航按钮图标; 1.2.5 在onOptionsItemSelected()中对HomeAsUp按钮的点击事件进行处理——调用DrawerLayout的openDrawer()方法将滑动菜单展示出来; 注意openDrawer()方法要求传入一个Gravity参数, 为了保证这里的行为和XML中(DrawerLayout标签下的第二个直接子控件的android:layout_gravity值)定义的一致, 我们传入了GravityCompat.START; 1.2.6 实际上Toolbar最左侧的这个按钮就叫作HomeAsUp按钮,它默认的图标是一个返回的箭头,含义是返回上一个活动; 这里将其换了图标,并将逻辑响应修改了; HomeAsUp按钮的id永远都是android.R.id.home!!! 2. NavigationView 控件用处:轻松布局华丽炫酷的滑动菜单页面; 2.1 添加了两行依赖关系 compile 'com.android.support:design:24.2.1' compile 'de.hdodenhof:circleimageview:2.1.0' 2.2 在开始使用NavigationView之前,我们还需要提前准备好两个东西:menu和headerLayout。 2.2.1 menu是用来在NavigationView中显示具体的菜单项的; 为Menu resource file; 在<menu>中嵌套了一个<group>标签 <group>标签下的<item>: android:id属性指定菜单项的id, android:icon属性指定菜单项的图标, android:title属性指定菜单项显示的文字。 2.2.2 headerLayout则是用来在NavigationView中显示头部布局的。 为Layout resourcefile; 2.3 使用NavigationView 添加android.support.design.widget.NavigationView标签, 使用app:menu="@menu/nav_menu" app:headerLayout="@layout/nav_header" 将menu和headerLayout设置完毕 正文 最终成果图 DrawerLayout 关于滑动菜单和DrawerLayout,郭神如是说: <?xml version="1.0" encoding="utf-8"?> <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent"> <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/> </FrameLayout> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="start" android:text="This is menu" android:textSize="30sp" android:background="#FFF"/> </android.support.v4.widget.DrawerLayout> 可见这里最外层的控件使用了DrawerLayout,这个控件是由support-v4库提供的。 DrawerLayout中放置了两个直接子控件: 第一个子控件是FrameLayout,用于作为主屏幕中显示的内容,当然里面还有我们刚刚定义的Toolbar。 第二个子控件这里使用了一个TextView,用于作为滑动菜单中显示的内容,其实使用什么都可以,DrawerLayout并没有限制只能使用固定的控件。 但是关于第二个子控件有一点需要注意,layout_gravity这个属性是必须指定的, 因为我们需要告诉DrawerLayout滑动菜单是在屏幕的左边还是右边, 指定left表示滑动菜单在左边; 指定right表示滑动菜单在右边; 这里指定了start,表示会根据系统语言进行判断,如果系统语言是从左往右的,比如英语、汉语,滑动菜单就在左边,如果系统语言是从右往左的,比如阿拉伯语,滑动菜单就在右边。 现在重新运行一下程序,然后在屏幕的左侧边缘向右拖动,就可以让滑动菜单显示出来了,如图: 这里我们并没有改动多少代码, 首先调用findViewById()方法得到了DrawerLayout的实例, 然后调用getSupportActionBar()方法得到了ActionBar的实例,虽然这个ActionBar的具体实现是由Toolbar来完成的。 接着调用ActionBar的setDisplayHomeAsUpEnabled()方法让导航按钮显示出来, 又调用了setHomeAsUpIndicator()方法来设置一个导航按钮图标。 实际上,Toolbar最左侧的这个按钮就叫作HomeAsUp按钮,它默认的图标是一个返回的箭头,含义是返回上一个活动。很明显,这里我们将它默认的样式(该按钮图标)和作用(改/设置了按钮点击事件)都进行了修改。 接下来在onOptionsItemSelected()方法中对HomeAsUp按钮的点击事件进行处理,HomeAsUp按钮的id永远都是android.R.id.home; 切记是android.R.id.home,如果写成R.id.home是实现不了功能的! 然后调用DrawerLayout的openDrawer()方法将滑动菜单展示出来; 注意openDrawer()方法要求传入一个Gravity参数,为了保证这里的行为和XML中定义的一致,我们传入了GravityCompat.START; 当前MainActivity全文: public class MainActivity extends AppCompatActivity { private DrawerLayout mDrawerLayout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); ActionBar actionBar = getSupportActionBar(); if(actionBar != null){ actionBar.setDisplayHomeAsUpEnabled(true);//让导航按钮显示出来 actionBar.setHomeAsUpIndicator(R.drawable.ic_menu);//设置一个导航按钮图标 } } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.toolbar,menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()){ case android.R.id.home: mDrawerLayout.openDrawer(GravityCompat.START); break; case R.id.backup: Toast.makeText(this,"You clicked Backup" , Toast.LENGTH_SHORT).show(); break; case R.id.delete: Toast.makeText(this,"You clicked Delete" , Toast.LENGTH_SHORT).show(); break; case R.id.settings: Toast.makeText(this,"You clicked Settings" , Toast.LENGTH_SHORT).show(); break; default: } return true; } } 运行程序,效果如下: 可见在Toolbar的最左边出现了一个导航按钮,用户看到这个按钮就知道这肯定是可以点击的。 现在点击一下这个按钮,滑动菜单界面就会再次展示出来了。 NavigationView 首先这个控件是DesignSupport库中提供的,需要将这个库引入到项目中。 打开app/build.gradle文件,在dependencies闭包中添加依赖: compile 'com.android.support:design:24.2.1' compile 'de.hdodenhof:circleimageview:2.1.0' 这里添加了两行依赖关系, 第一行就是DesignSupport库, 第二行是一个开源项目CircleImageView,它可以用来轻松实现图片圆形化的功能,我们待会就会用到它。CircleImageView的项目主页地址是:https://github.com/hdodenhof/CircleImageView。 !!! 在开始使用NavigationView之前,我们还需要提前准备好两个东西:menu和headerLayout。menu是用来在NavigationView中显示具体的菜单项的;headerLayout则是用来在NavigationView中显示头部布局的。 1/4.准备menu 我们先来准备menu,这里我事先找了几张图片来作为按钮的图标,并将它们放在了drawable-xxhdpi目录下。然后右击menu文件夹→New→Menu resource file,创建一个nav_menu.xml文件,并编写如下代码: <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <group android:checkableBehavior="single"> <item android:id="@+id/nav_call" android:icon="@drawable/nav_call" android:title="Call"/> <item android:id="@+id/nav_friends" android:icon="@drawable/nav_friends" android:title="Friends"/> <item android:id="@+id/nav_location" android:icon="@drawable/nav_location" android:title="Location"/> <item android:id="@+id/nav_mail" android:icon="@drawable/nav_mail" android:title="Mail"/> <item android:id="@+id/nav_task" android:icon="@drawable/nav_task" android:title="Tasks"/> </group> </menu> 我们首先在<menu>中嵌套了一个<group>标签, 然后将group的checkableBehavior属性指定为singlegroup表示一个组, checkableBehavior指定为single表示组中的所有菜单项只能单选; 那么下面我们来看一下这些菜单项吧。这里一共定义了5个item, 分别使用 android:id属性指定菜单项的id, android:icon属性指定菜单项的图标, android:title属性指定菜单项显示的文字。 就是这么简单,现在我们已经把menu准备好了。 2/4.准备headerLayout 接下来应该准备headerLayout了,这是一个可以随意定制的布局,不过这里不将它做得太复杂。我们就在headerLayout中放置头像、用户名、邮箱地址这3项内容吧; 说到头像,那我们还需要再准备一张图片,这里找了一张宠物图片,并把它放在了drawable-xxhdpi目录下。 另外这张图片最好是一张正方形图片,因为待会我们会把它圆形化。 然后右击layout文件夹→New→Layout resourcefile,创建一个nav_header.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="180dp" android:padding="10dp" android:background="?attr/colorPrimary"> <de.hdodenhof.circleimageview.CircleImageView android:id="@+id/icon_image" android:layout_width="70dp" android:layout_height="70dp" android:src="@drawable/nav_icon" android:layout_centerInParent="true"/> <TextView android:id="@+id/mail" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:text="8267*****@qq.com" android:textColor="#FFF" android:textSize="14sp"/> <TextView android:id="@+id/username" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@id/mail" android:text="wanchuangxiaoyun" android:textColor="#FFF" android:textSize="14sp"/> </RelativeLayout> 可以看到,布局文件的最外层是一个RelativeLayout,我们将它的 宽度设为match_parent, 高度设为180dp, 这是一个NavigationView比较适合的高度,然后 指定它的背景色为colorPrimary; 在RelativeLayout中我们放置了3个控件, CircleImageView是一个用于将图片圆形化的控件,它的用法非常简单,基本和ImageView是完全一样的,这里给它指定了一张图片作为头像,然后设置为居中显示。 另外两个TextView分别用于显示用户名和邮箱地址,它们都用到了一些RelativeLayout的定位属性; 3/4.使用NavigationView 现在menu和headerLayout都准备好了,我们终于可以使用NavigationView了。 修改activitymam.xml中的代码,如下所示: <?xml version="1.0" encoding="utf-8"?> <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent"> <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/> </FrameLayout> <android.support.design.widget.NavigationView android:id="@+id/nav_view" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="start" app:menu="@menu/nav_menu" app:headerLayout="@layout/nav_header"> </android.support.design.widget.NavigationView> </android.support.v4.widget.DrawerLayout> 可见这里将之前的TextView换成了NavigationView,这样滑动菜单中显示的内容也就变成NavigationView了。 这里又通过app:menu和app:headerLayout将我们刚才准备好的menu和headerLayout设置了进去, android:layout_gravity="start"则是跟上面的textview一个道理了,用于指明滑动菜单的滑动位置, 这样NavigationView就定义完成了。 接下来还要去处理菜单项的点击事件。修改MainActivity中的代码: 代码还是比较简单的, 这里首先获取到了NavigauonView的实例, 然后调用它的setCheckedItem()方法将Call菜单项设置为默认选中。 接着调用了setNavigationItemSelectedListener()方法来设置一个菜单项选中事件的监听器,当用户点击了任意菜单项时,就会回调到onNavigationItemSelected()方法中。 我们可以在这个方法中写相应的逻辑处理,不过这里并没有附加任何逻辑,只是调用了DrawerLayout的closeDrawers()方法将滑动菜单关闭,这也是合情合理的做法。 下面是当前MainActivity.java的全文: public class MainActivity extends AppCompatActivity { private DrawerLayout mDrawerLayout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); NavigationView navView = (NavigationView) findViewById(R.id.nav_view); ActionBar actionBar = getSupportActionBar(); if(actionBar != null){ actionBar.setDisplayHomeAsUpEnabled(true);//让导航按钮显示出来 actionBar.setHomeAsUpIndicator(R.drawable.ic_menu);//设置一个导航按钮图标 } navView.setCheckedItem(R.id.nav_call);//将Call菜单项设置为默认选中 navView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener(){ @Override public boolean onNavigationItemSelected(@NonNull MenuItem item) { mDrawerLayout.closeDrawers();//关闭滑动菜单 return true; } }); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.toolbar,menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()){ case R.id.home: mDrawerLayout.openDrawer(GravityCompat.START); break; case R.id.backup: Toast.makeText(this,"You clicked Backup" , Toast.LENGTH_SHORT).show(); break; case R.id.delete: Toast.makeText(this,"You clicked Delete" , Toast.LENGTH_SHORT).show(); break; case R.id.settings: Toast.makeText(this,"You clicked Settings" , Toast.LENGTH_SHORT).show(); break; default: } return true; } } 现在可以重新运行一下程序了,点击一下Toolbar左侧的导航按钮,效果如图所示: 怎么样?这样的滑动菜单页面,你无论如何也不能说它丑了吧?MaterialDesign的魅力就在 这里,它真的是一种非常美观的设计理念,只要你按照它的各种规范和建议来设计界面,最终做 出来的程序就是特别好看的。——郭霖大神
本模块共有六篇文章,参考郭神的《第一行代码》,对Material Design的学习做一个详细的笔记,大家可以一起交流一下: Material Design 实战 之第一弹——Toolbar(即本文) Material Design 实战 之第二弹——滑动菜单详解&实战 Material Design 实战 之第三弹—— 悬浮按钮和可交互提示(FloatingActionButton & Snackbar & CoordinatorLayout) Material Design 实战 之第四弹 —— 卡片布局以及灵动的标题栏(CardView & AppBarLayout) Material Design 实战 之第五弹 —— 下拉刷新(SwipeRefreshLayout) Material Design 实战 之 第六弹 —— 可折叠式标题栏(CollapsingToolbarLayout) & 系统差异型的功能实现(充分利用系统状态栏空间) 写在文首,什么是Material Design?这里参考一下郭神的说法: 文章提要与总结 1. 关于android:theme的详细理解(附图); (运用:使用Toolbar代替ActionBar时,须将Theme.AppCompat.Light.DarkActionBar改成Theme.AppCompat.Light.NoActionBar) 2. 在activity_main.xml中使用Toolbar代替ActionBar; 关于命名空间:android app; 关于Toolbar控件的属性; 尤其android:theme以及app:popupTheme的用法理解; 3. 关于activity.android:label; 4. 通过Menu resource file菜单文件式(同时为xml格式)来为Toolbar添加action按钮; 文件中: <item>标签来定义action按钮, android:id用于指定按钮的id, android:icon用于指定按钮的图标, android:title用于指定按钮的文字; 使用app:showAsAction来指定按钮的显示位置,再次使用了app命名空间,同样是为了能够兼容低版本的系统; showAsAction的几种选值:always ifRoom never 注意, Toolbar中的action按钮只会显示图标, 菜单中的action按钮只会显示文字; 5. onCreateOptionsMenu()加载描述Toolbar的action按钮的Menu resource file; onOptionsItemSelected()方法中处理各个按钮的点击事件; 正文 关于Toolbar Toolbar相关于ActionBar;不过ActionBar由于其设计原因,被限定只能位于活动的顶部,从而不能实现一些Material Design的效果,因此官方现在已经不建议使用ActionBar了。 首先要知道,任何一个新建的项目默认都是会显示ActionBar的。 ActionBar是根据项目中指定的主题来显示, 打开AndroidManifest.xml文件: 可以看到这里用android:theme指定了一个AppTheme的主题。 打开res/values/styles.xml文件可以查看其定义出处: 这里定义了一个叫AppTheme的主题并指定它的parent主题是Theme.AppCompat.Light.DarkActionBar。 DarkActionBar是一个深色的ActionBar主题,所有的新建项目中自带的ActionBar就是因为指定了这个主题才出现的。 要使用TooIbar来替代ActionBar时需要指定一个不带ActionBar的主题, 对应的通常有Theme.AppCompat.NoActionBar和Theme.AppCompat.Light.NoActionBar这两种主题可选; 其中: Theme.AppCompat.NoActionBar表示深色主题,它会将界面的主体颜色设成深色,陪衬颜色设成淡色; Theme.AppCompat.Light.NoActionBar表示淡色主题,它会将界面的主体颜色设成淡色,陪衬颜色设成深色。 具体可以根据情况去设置,这里主要选用淡色主题,如下所示: 观察代码中AppTheme中的属性重写: 可见这里重写了colorPrimary、colorPrimaryDark和colorAccent这3个属性的颜色。 属性指定颜色名称对应的位置,如图: 可见除了上述3个属性之外,我们还可以通过textColorPrimary、windowBackground和navigationBarCotor等属性来控制更多位置的颜色。 不过colorAccent这个属性比较特殊,它不只是用来指定这样一个按钮的颜色,而是更多表达了一个强调的意思,比如一些控件的选中状态也会使用colorAccent的颜色。 至此已将ActionBar隐藏,接下来使用Toolbar来替代ActionBar。 修改activity_main.xml: <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/> </FrameLayout> 下面解析一下这段代码: 首先看一下第2行,这里使用xmlns:app指定了一个新的命名空间。 正是由于每个布局文件都会使用xmlns:android来指定一个命名空间,因此我们才能一直使用android:id、android:layoutwidth等写法, 这里指定了xmlns:app,现在便可以使用app:attribute这样的写法了。 但是为什么这里要指定一个xmlns:app的命名空间呢? 这是由于MaterialDesign是在Android5.0系统中才出现的,而很多的Material属性在5.0之前的系统中并不存在,那么为了能够兼容之前的老系统,我们就不能使用android:attribute这样的写法了,而是应该使用app:attribute 接下来定义了一个Toolbar控件,这个控件是由appcompat-v7库提供的。 这里我们给Toolbar指定了一个id,将它的 宽度设置为matchparent, 高度设置为actionBar的高度, 背景色设置为colorPrimary。 接下来,关于主题: 由于我们刚才在styles.xml中将程序的主题指定成了淡色主题,因此Toolbar现在也是淡色主题(“蓝底(黑字)”),而TooIbar上面的各种元素就会自动使用深色主题(“(黑底)X字”),这是为了和主体颜色区别开(具体可以看文章开头对于深色浅色主题的解释)。 在styles.xml中将程序的主题指定成了淡色主题,Toolbar现在也是淡色主题,TooIbar上面的各种元素就会自动使用深色主题 但是这个效果看起来就会很差,之前使用ActionBar时文字都是白色的,现在变成黑色的会很难看。那么为了能让Toolbar单独(全局是用由APPTheme制定的浅色主题的,故相对而言这里用“单独”)使用深色主题,这里我们使用android:theme属性,将Toolbar的主题指定成了ThemeOverlay.AppCompat.Dark.ActionBar。 但是这样指定完了之后又会出现新的问题,如果Toolbar中有菜单按钮,那么弹出的菜单项也会变成深色主题,这样就再次变得十分难看,于是这里使用了app:popupTheme属性单独将弹出的菜单项指定成了淡色主题。 之所以使用app:popupTheme,是因为popupTheme这个属性是在Android5.0系统中新增的,我们使用app:popupTheme的话就可以兼容Android5.0以下的系统了。 小结: 为了能够兼容之前的老系统,使用app:attribute,而不是android:attribute; 在styles.xml中将程序的主题指定成了淡色主题; 使用android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"让Toolbar单独使用深色主题; 使用app:popupTheme="@style/ThemeOverlay.AppCompat.Light"单独将弹出的菜单项指定成了淡色主题; 之所以使用app:popupTheme,是因为popupTheme这个属性是在Android5.0系统中新增的,我们使用app:popupTheme的话就可以兼容Android5.0以下的系统了。 接下来修改MainActivity: protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); } 这里, 首先用findViewByid()得到Toolbar的实例, 然后调用setSupportActionBar()方法同时传入Toolbar的实例, 至此便实现了既使用Toolbar,又让它的外观与功能都同ActionBar一致。 现在运行一下程序,效果如图: 这个标题栏虽然看上去和之前的没什么两样,但其实它已经是Toolbar而不是ActionBar了,它现在也具备了实现MaterialDesign效果的能力。 接着实战一些Toolbar比较常用的功能,比如修改标题栏上显示的文字内容, 这段文字是在AndroidManifest.xml中指定的,如下所示: 这里给activity增加了一个android:label属性,用于指定在Toolbar中显示的文字内容, 如果没有指定的话,会默认使用application中指定的label内容,也就是我们的应用名称。 接下来再添加一些action按钮来丰富Toolbar: 先准备了几张图片来作为按钮的图标,将它们放在了drawable-xxhdpi目录下; 右击res目录→New→Directory,创建一个menu文件夹; 右击menu文件夹→New→Menuresourcefile,创建一个toolbar.xml文件并编写: <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/backup" android:icon="@drawable/ic_backup" android:title="Backup" app:showAsAction="always"/> <item android:id="@+id/delete" android:icon="@drawable/ic_delete" android:title="Delete" app:showAsAction="ifRoom"/> <item android:id="@+id/settings" android:icon="@drawable/ic_settings" android:title="Settings" app:showAsAction="never"/> </menu> 可以看到,我们通过 <item>标签来定义action按钮, android:id用于指定按钮的id, android:icon用于指定按钮的图标, android:title用于指定按钮的文字。 接着使用app:showAsAction来指定按钮的显示位置, 之所以这里再次使用了app命名空间,同样是为了能够兼容低版本的系统。 showAsAction主要有以下几种值可选: always表示永远显示在Toolbar中,如果屏幕空间不够则不显示; ifRoom表示屏幕空间足够的情况下显示在Toolbar中,不够的话就显示在菜单当中; never则表示永远显示在菜单当中。 注意,Toolbar中的action按钮只会显示图标, 菜单中的action按钮只会显示文字。 接下来就是创建菜单的套路了,修改MainActivity中的代码,如下所示: package com.example.materialtest; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.support.v7.widget.Toolbar; import android.view.Menu; import android.view.MenuItem; import android.widget.Toast; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.toolbar,menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()){ case R.id.backup: Toast.makeText(this,"You clicked Backup" , Toast.LENGTH_SHORT).show(); break; case R.id.delete: Toast.makeText(this,"You clicked Delete" , Toast.LENGTH_SHORT).show(); break; case R.id.settings: Toast.makeText(this,"You clicked Settings" , Toast.LENGTH_SHORT).show(); break; default: } return true; } } 上述代码: 在onCreate0ptionsMenu()中加载toolbar.xml这个菜单文件, 在onOptionsItemSelected()中处理各个按钮的点击事件。 重新运行一下程序,效果如图: 可见Toolbar上面现在显示了两个action按钮,这是因为 Backup按钮指定的显示位置是alway, Delete按钮指定的显示位置是ifRoom,而现在屏幕空间充足。因此两个按钮都会显示在Toolbar中。 Settings按钮则由于指定的显示位置是never,所以显示在菜单中(点击最右边的三个小点即知)。 同时注意这些action按钮都是可以响应点击事件的!
ROS官网 ROS的kinetic安装 手把手式教程,小伙伴们大可放心,慢慢来 下面运行一个课堂上的例子: 环境:ubuntu16.04 & ROS-Ubuntu2 ROS初体验——乌龟爬爬爬 输入以下命令启动节点: roscore 出现最后一行的started core service 即成功运行。 输入以下命令生成小乌龟: rosrun turtlesim turtlesim_node 输入以下命令可以使用键盘上的箭头按键控制乌龟移动: rosrun turtlesim turtle_teleop_key 注意两点: 1.以上三条命令需要分别依次打开三个终端窗口进行输入执行; 2.需要将光标焦点停留在最后一个(即第三个)终端窗口上才可调动乌龟; 运行结果如图: 还有可能遇到下面这个报错: 解决方案:http://www.cnblogs.com/fuhang/p/9689815.html即输入下面代码,再次运行roscore解决: sudo chmod 777 -R ~/.ros/
本例编写一个对应芯片的延时函数并调用之: 打开Keil uVision4,点击project,New一个uVision4 Project: 放在test文件下: 选择芯片: ctrl+N 创建三个文件(main.c/delay.c/Delay.h),这里debug一下头文件和源文件的联编,实际上也可以将Delay.h以及delay.c的内容放进main.c中: #include <reg51.h> #include <Delay.h> void main(void) { P2=0x0f; while(1) { Delay10ms(100); P2=~P2; } } 该芯片的延时10ms的函数: Delay10ms(int ms) { int i; unsigned char tem; for(i=0;i<ms;i++) for(tem=0;tem<120;tem++){} } #ifndef _DELAY_H_ #define _DELAY_H_ void Delay10ms(int ms); #endif 文件编写完毕,右键文件夹Source Group 1,使用Add Files to Group 'Source Group 1'的方式依次将文件添加进目录: 下面是文件目录: 编译:
看《第一行代码》的时候,网络测试这一块需要下载一个Apache服务器,书上说百度上一找就够,下载之后一路next就好对吧。。。别想了,那个时代已经过去了唉,那现在要咋办,小伙伴不急,继续往下看。 1.从Apache官网下载windows版Apache服务器 戳这里上Apache官网进入官网后,点击中间“Downloading the Apache HTTP Server”下面的“a number of third party vendors”选项。 待页面跳转后,点击“Downloading Apache for Windows ”下面的“ApacheHaus”选项。 打开ApacheHaus之后网站上会显示各种windows版本,此时选择你需要的版本,左键单击。 此时第一个为32位的版本,第二个后面标有“x64”的为64位的版本,选择自己需要的,点击后面红框中的下载。 打开Apache所下载到的文件夹,显示为一个压缩包。 2.从安装到运行 将之解压: 在C:\Windows\SysWOW64中,找到cmd.exe并以‘管理员的身份’运行。 (如果不以管理员的身份运行,在输入命令:httpd -k install 时,会出现 ‘AH00369: Failed to open the Windows service manager’错误)。打开cmd之后,进入apache文件夹目录:输入: httpd -k install 当然这是还有可能出现一个错误:“ ServerRoot must be a valid directory” 这是因httpd.conf里面配置的ServerRoot路径跟实际路径不一致所引起的。 解决方式是用记事本形式打开..\Apache24\conf\httpd.conf 文件,查找SRVROOT(只有一处): 查找结果如下(将其后面的双引号里面的路径改为Apache的实际解压路径后保存即可): 完毕之后再次在命令行输入httpd -k install,搞定! 接下来,启动服务! 找到Apache安装目录中“bin”文件夹下的“ApacheMonitor”,双击运行: 在右下角任务栏左键应用,点击“start”,启动“Apache”服务: 或者右键应用,进行相应的操作即可: Open Apache Monitor,此时我们可以看到服务已经启动: 这里还可以命令行输入命令:net start Apache2.4,启动Apache服务器; 或者使用bin\ApacheMonitor.exe启动服务。 浏览器输入http://localhost/ 或者 127.0.0.1,显示htdocs\index.html页面: 3.使用的demo 找到htdocs文件下,在下面随便放一个html文件噜,我这里放的是前阵子写的博文里面的demo,即《JavaScript_note》,戳这里走着嘞; 完了之后在浏览器键入以下地址: 127.0.0.1/demo.html 一个回车,便成功访问了: 4.卸载 卸载主要的步骤是: 禁用服务; 卸载服务; 删除软件; 若Apache服务器软件不想用了,想要卸载,需要先卸载apache服务(若直接删除安装路径的文件夹,会有残余文件在电脑,可能会造成不必要的麻烦),在cmd命令窗口,输入如下(建议先停止服务再删除):sc delete apache(apache是Apache服务器的服务名)。 文章到此结束,打码愉快噢~ 另外可供参考的文章: 如何从Apache官网下载windows版apache服务器——从下载到运行; 第一行代码 第九章 网络技术 - 搭建Apache服务器——杂症记录 关于使用android模拟器访问本地服务器失败——模拟器访问问题解决方案 同上 百度经验:如何卸载apache Apache服务器从下载到卸载
Content Provider 总结: 跨程序共享数据——Content Provider 之 运行时权限解析以及申请的实现(可完美解决java.lang.SecurityException:Permission Denial 问题) 跨程序共享数据——Content Provider 之 ContentResolver基本用法 & 一个读取系统联系人的Demo 跨程序共享数据——Content Provider 之 创建自己的内容提供器 Content Provider 之 最终弹 实战体验跨程序数据共享(结合SQLiteDemo)(即本文) Java回顾: Java基础知识的全面巩固_note1(附各种demo code) Java知识详细巩固_note2(数组 _ 附demo code) 受腾讯云+社区的运营小编陈子龙前辈邀请, 我的博客即将搬运同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=2iitqqr04f408 ——2018.9.3
本模块共有四篇文章,参考郭神的《第一行代码》,对Content Provider的学习做一个详细的笔记,大家可以一起交流一下: 跨程序共享数据——Content Provider 之 运行时权限解析以及申请的实现(可完美解决java.lang.SecurityException:Permission Denial 问题) 跨程序共享数据——Content Provider 之 ContentResolver基本用法 & 一个读取系统联系人的Demo 跨程序共享数据——Content Provider 之 创建自己的内容提供器 Content Provider 之 最终弹 实战体验跨程序数据共享(结合SQLiteDemo)(即本文) 简单起见,我们还是在之前的DatabaseTest项目(点击前往码云地址)的基础上继续开发。 需求是:通过内容提供器来给它加人外部访问接口。 程序设计的步骤: 1.在A程序中注册内容提供器,写好接口处理方法; 具体的,全局变量:定义自定义代码常量,定义authority常量,声明uriMatcher和DatabaseHelper对象; 1.1 内容提供器中增删改查的程序设计步骤为: 1.1.1 调用实例化DatabaseHelper的get方法获得SQLiteDatabase实例化对象; (get方法即getWritableDatabase或者getReadableDatabase) 1.1.2 接着,query需定义一个Cursor对象(cursor)用于接收返回结果; insert需定义一个Uri对象(urireturn)用于接收insert新增的数据行的uri update需定义一个int对象(updatedRows)用于接收受影响的行数; delete需定义一个int对象(deletedRows)用于接收受影响的行数(被删除的行数); 1.1.3 使用switch语句进行对uri的判断及判断结果的处理; 2.在需要访问A程序的内容提供器的程序中,构建对应的Uri,通过getContentResolver调用增删改查即可; 下面开始详细解析:打开DatabaseTest项目(点击前往码云地址),首先将MyDatabaseHelper中使用Toast弹出创建数据库成功的提示去除掉,因为跨程序访问时我们不能直接使用Toast(!!!!!)。然后创建一个内容提供器,右击com.example.databasetest包—New—Other—ContentProvider, 会弹出如图所示: 这里我们将内容提供器命名为DatabaseProvider, authority指定为com.example.databasetest.provider, Exported属性表示是否允许外部程序访问我们的内容提供器, Enabled属性表示是否启用这个内容提供器。 将两个属性都勾中,点击Finish完成创建。 接着我们修改DatabaseProvider: package com.example.databasetest; import android.content.ContentProvider; import android.content.ContentValues; import android.content.UriMatcher; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.net.Uri; public class DatabaseProvider extends ContentProvider { public static final int BOOK_DIR = 0; public static final int BOOK_ITEM = 1; public static final int CATEGORY_DIR = 2; public static final int CATEGORY_ITEM = 3; public static final String AUTHORITY = "com.example.databasetest.provider"; private static UriMatcher uriMatcher; private MyDatabaseHelper dbHelper; static { uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); uriMatcher.addURI(AUTHORITY, "book", BOOK_DIR); uriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM); uriMatcher.addURI(AUTHORITY, "category", CATEGORY_DIR); uriMatcher.addURI(AUTHORITY, "category/#", CATEGORY_ITEM); } @Override public boolean onCreate() { dbHelper = new MyDatabaseHelper(getContext(), "BookStore.db", null, 2); return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { // 查询数据 SQLiteDatabase db = dbHelper.getReadableDatabase(); Cursor cursor = null; switch (uriMatcher.match(uri)) { case BOOK_DIR: cursor = db.query("Book", projection, selection, selectionArgs, null, null, sortOrder); break; case BOOK_ITEM: String bookId = uri.getPathSegments().get(1); cursor = db.query("Book", projection, "id = ?", new String[] { bookId }, null, null, sortOrder); break; case CATEGORY_DIR: cursor = db.query("Category", projection, selection, selectionArgs, null, null, sortOrder); break; case CATEGORY_ITEM: String categoryId = uri.getPathSegments().get(1); cursor = db.query("Category", projection, "id = ?", new String[] { categoryId }, null, null, sortOrder); break; default: break; } return cursor; } @Override public Uri insert(Uri uri, ContentValues values) { // 添加数据 SQLiteDatabase db = dbHelper.getWritableDatabase(); Uri uriReturn = null; switch (uriMatcher.match(uri)) { case BOOK_DIR: case BOOK_ITEM: long newBookId = db.insert("Book", null, values); //插入后返回一个id!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! uriReturn = Uri.parse("content://" + AUTHORITY + "/book/" + newBookId);//id构造用于返回的URI!!!!!!!!!!!!!!!!!!!!!! break; case CATEGORY_DIR: case CATEGORY_ITEM: long newCategoryId = db.insert("Category", null, values); uriReturn = Uri.parse("content://" + AUTHORITY + "/category/" + newCategoryId); break; default: break; } return uriReturn; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { // 更新数据 SQLiteDatabase db = dbHelper.getWritableDatabase(); int updatedRows = 0; switch (uriMatcher.match(uri)) { case BOOK_DIR: updatedRows = db.update("Book", values, selection, selectionArgs); break; case BOOK_ITEM: String bookId = uri.getPathSegments().get(1); updatedRows = db.update("Book", values, "id = ?", new String[] { bookId }); break; case CATEGORY_DIR: updatedRows = db.update("Category", values, selection, selectionArgs); break; case CATEGORY_ITEM: String categoryId = uri.getPathSegments().get(1); updatedRows = db.update("Category", values, "id = ?", new String[] { categoryId }); break; default: break; } return updatedRows; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { // 删除数据 SQLiteDatabase db = dbHelper.getWritableDatabase(); int deletedRows = 0; switch (uriMatcher.match(uri)) { case BOOK_DIR: deletedRows = db.delete("Book", selection, selectionArgs); break; case BOOK_ITEM: String bookId = uri.getPathSegments().get(1); deletedRows = db.delete("Book", "id = ?", new String[] { bookId }); break; case CATEGORY_DIR: deletedRows = db.delete("Category", selection, selectionArgs); break; case CATEGORY_ITEM: String categoryId = uri.getPathSegments().get(1); deletedRows = db.delete("Category", "id = ?", new String[] { categoryId }); break; default: break; } return deletedRows; } @Override public String getType(Uri uri) { switch (uriMatcher.match(uri)) { case BOOK_DIR: return "vnd.android.cursor.dir/vnd.com.example.databasetest. provider.book"; case BOOK_ITEM: return "vnd.android.cursor.item/vnd.com.example.databasetest. provider.book"; case CATEGORY_DIR: return "vnd.android.cursor.dir/vnd.com.example.databasetest. provider.category"; case CATEGORY_ITEM: return "vnd.android.cursor.item/vnd.com.example.databasetest. provider.category"; } return null; } } 代码虽然很长,不过都不难理解,都是上一节学习过的内容。 首先在类的一开始,同样是定义了4个常量,分别用于表示 访问Book表中的所有数据; 访问Book表中的单条数据; 访问Category表中的所有数据; 访问Category表中的单条数据。 然后在静态代码块里对UriMatcher进行了初始化操作,将期望匹配的几种URI格式添加了进去。 接下来是onCreate()方法: 创建了一个MyDatabaseHelper的实例,然后返回true表示内容提供器初始化成功,这时数据库就已经完成了创建或升级操作。 接着是query()方法: 首先获取到SQLiteDatabase实例,然后根据传入的Uri参数判断出用户想要访问哪张表, 再调用SQLiteDatabase的query()进行查询,并将Cursor对象返回即可。 注意当访问单条数据的时候有一个细节: 这里调用了Uri对象的getpathSegments()方法,它会将内容URI权限之后的部分以“/“符号进行分割,并把分割后 的结果放入到一个字符串列表中,那这个列表的第0个位置存放的就是路径,第1个位置存放的就是id了。 得到了id之后,再通过selection和selectionArgs参数进行约束,就实现了查询单条数据的功能。 getPathSegments().get(1)的解释参考: 然后是insert()方法: 同样先获取到SQLiteDatabase实例, 然后根据传入的Uri参数判断出用户想要往哪张表里添加数据, 再调用SQLiteDatabase的insert()方法进行添加即可。 注意insert()方法要求返回一个能够表示这条新增数据的URI,则这里还需调用Uri.parse()方法来将一个内容URI解析成Uri对象,当然这个内容URI是以新增数据的id结尾的。 接着是update()方法: 先获取SQLiteDatabase实例, 然后根据传入的Uri参数判断出用户想要更新哪张表里的数据, 再调用SQLiteDatabase的update()方法进行更新即可,受影响的行数作为返回值返回。 然后是delete()方法: 先获取到SQLiteDatabase的实例, 然后根据传入的Uri参数判断出用户想要删除哪张表里的数据, 再调用SQLiteDatabase的delete()方法进行删除即可,被删除的行数作为返回值返回。 最后是getType()方法,这里按照上一节中介绍的格式规则编写即可。 至此内容提供器中的代码便全部编写完了。 另外!!内容提供器一定要在AndroidMamfest.xml文件中注册才可以使用, 不过使用Androidstudio的快捷方式创建内容提供器的话,注册会被自动完成。 打开AndroidManifest.xmI文件看一下: 可以看到<application>标签内出现了一个新的标签<provider>,我们使用它来注册内容提供器DatabaseProvider。 其中android:name指定DatabaseProvider的类名, android:authorities指定了DatabaseProvider的authority, enabled和exported属性则是根据我们刚才勾选的状态自动生成的。 现在DatabaseTest这个项目便具备跨程序共享数据的功能了。 可以调试一下: 首先将DatabaseTest程序从模拟器中删除掉,防止遗留数据的造成干扰。 然后运行一下项目,将DatabaseTest程序重新安装在模拟器上。 接着关闭掉DatabaseTest这个项目,并创建一个新项目ProviderTest, 接着通过这个程序去访问DatabaseTest中的数据, 先编写布局文件: <?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" > <Button android:id="@+id/add_data" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Add To Book" /> <Button android:id="@+id/query_data" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Query From Book" /> <Button android:id="@+id/update_data" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Update Book" /> <Button android:id="@+id/delete_data" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Delete From Book" /> </LinearLayout> MainActivity.java: package com.example.providertest; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; public class MainActivity extends AppCompatActivity { private String newId; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button addData = (Button) findViewById(R.id.add_data); addData.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 添加数据 Uri uri = Uri.parse("content://com.example.databasetest.provider/book"); ContentValues values = new ContentValues(); values.put("name", "A Clash of Kings"); values.put("author", "George Martin"); values.put("pages", 1040); values.put("price", 55.55); Uri newUri = getContentResolver().insert(uri, values); newId = newUri.getPathSegments().get(1); } }); Button queryData = (Button) findViewById(R.id.query_data); queryData.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 查询数据 Uri uri = Uri.parse("content://com.example.databasetest.provider/book"); Cursor cursor = getContentResolver().query(uri, null, null, null, null); if (cursor != null) { while (cursor.moveToNext()) { String name = cursor.getString(cursor. getColumnIndex("name")); String author = cursor.getString(cursor. getColumnIndex("author")); int pages = cursor.getInt(cursor.getColumnIndex ("pages")); double price = cursor.getDouble(cursor. getColumnIndex("price")); Log.d("MainActivity", "book name is " + name); Log.d("MainActivity", "book author is " + author); Log.d("MainActivity", "book pages is " + pages); Log.d("MainActivity", "book price is " + price); } cursor.close(); } } }); Button updateData = (Button) findViewById(R.id.update_data); updateData.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 更新数据 Uri uri = Uri.parse("content://com.example.databasetest.provider/book/" + newId); ContentValues values = new ContentValues(); values.put("name", "A Storm of Swords"); values.put("pages", 1216); values.put("price", 24.05); getContentResolver().update(uri, values, null, null); } }); Button deleteData = (Button) findViewById(R.id.delete_data); deleteData.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 删除数据 Uri uri = Uri.parse("content://com.example.databasetest.provider/book/" + newId); getContentResolver().delete(uri, null, null); } }); } } 其中值得注意的是: 从以上代码中,我们可以看到DIR类型常量匹配的,selection, selectionArgs参数位是由调用本内容提供器的时候由调用方程序提供的,而ITEM则不同,uri中已经包含了ID,我们可以使用getPathSegments将之get出来再使用,即ITEM类型常量匹配的,调用方程序无需提供selection, selectionArgs参数位(如下方的代码截图),uri中已经包含了信息,处理方法也在内容提供器中写好了。(这一点在调用getContentResolver().update()以及getContentResolver().delete()的时候都一样) 下面进行代码的简析:分别在这4个按钮的点击事件里面处理了增删改查的逻辑: 添加数据的时候: 首先调用Uri.parse()将内容URI解析成Uri对象, 把要添加的数据存放到ContentValues对象中, 调用ContentResolver的insert()方法执行添加操作即可。 注意insert()会返回一个Uri对象,这个对象中包含了新增数据的id,这里用getPathSegments()将这个id取出,稍后会用到它; 查询数据的时候: 调用Uri.parse()将内容URI解析成Uri对象, 调用ContentResolver的query()方法去查询数据, 查询的结果存放在Cursor对象中, 对Cursor进行遍历,从中取出查询结果,并一一打印出来; 更新数据的时候: 调用Uri.parse()将内容URI解析成Uri对象, 把想要更新的数据存放到ContentValues对象中, 调用ContentResolver的update()方法执行更新操作即可; 注意这里为了不让Book表中的其他行受到影响, 在调用Uri.parse()方法时,给内容URI的尾部增加了一个id,而这个id正是添加数据时所返回的。 也就是说这里只更新刚刚添加的那条数据,不受影响Book表中的其他行。 删除数据的时候, 解析一个以id结尾的内容URI, 调用ContentResolver的delete()方法执行删除操作就可以了, 由于我们在内容URI里指定了一个id,因此只会删掉拥有相应id的那行数据,不会影响Book表中的其他数据。 现在运行一下ProviderTest项目,效果图如下: 点击一下AddToBook按钮,此时数据便已经添加到DatabaseTest程序的数据库中了, 可以点击QueryFromBook按钮来检查一下,打印日志如图: 这里可以看到DatabaseTest程序中只有我们刚刚添加的一条数据,databaseTest的SQLite数据库是我们在点击Add To Book的时候,试图访问DatabaseTest的内容提供器,由此DatabaseTest的内容提供器(DatabaseProvider)会触发DatabaseProvider.java中的onCreate()方法,如下,由此创建了数据库,并返回True,这一点在 跨程序共享数据——Content Provider 之 创建自己的内容提供器中曾经有提及;创建了数据库之后,便添加了添加了一条数据,由此DatabaseTest程序中只有我们刚刚添加的那一条数据而已。 点击一下Update Book按钮来更新数据,再点击一下Query From Book按钮进行检查,结果如图: 最后点击Delete From Book按钮删除数据,此时再点击Query From Book按钮就查询不到数据了。 至此跨程序共享数据功能便成功实现了。 现在不仅是ProviderTest程序,任何一个程序都可以轻松访问DatabaseTest中的数据,同时丝毫不用担心隐私数据泄漏的问题。