导航条

我想实现的效果是这样的

类似于ViewPager的效果,子类导航页面可以滑动,当滑动某个子类导航页面,导航线会平滑地向父类导航移动

·添加布局

<!--导航分类:编程语言/技术文档/源码下载-->
<LinearLayout
android:id="@+id/homepage_nav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_below="@+id/vp_homePageAd"
>
<TextView
android:id="@+id/homepage_nav_prog"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="编程语言"
android:textSize="17dp"
android:textStyle="bold"
android:layout_weight="1"
android:gravity="center"
/>
<TextView
android:id="@+id/homepage_nav_doc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="技术文档"
android:textStyle="bold"
android:layout_weight="1"
android:gravity="center"
/>
<TextView
android:id="@+id/homepage_nav_source"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="源码下载"
android:textStyle="bold"
android:layout_weight="1"
android:gravity="center"
/>
</LinearLayout>
<!-- 两条线-->
<View
android:id="@+id/homepage_nav_bottomLine"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_below="@+id/homepage_nav"
android:background="#88888888"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"/>
<!-- 移动的线-->
<View
android:id="@+id/homepage_nav_moveLine"
android:layout_width="70dp"
android:layout_height="3dp"
android:layout_alignBottom="@+id/homepage_nav_bottomLine"
android:background="#000000" />
<!--导航分类子类, 放在ViewPager中-->
<androidx.viewpager.widget.ViewPager
android:id="@+id/vp_homePageNav"
android:layout_width="match_parent"
android:layout_height="200px"
android:layout_below="@+id/homepage_nav_moveLine"
android:layout_marginTop="20px"/> </RelativeLayout>

·设置ViewPager的滑动页面

页面有若干item组成,每个item按样式是一张图片,下面加文字。那据说有一种比较高级的实现方法,就是外边一个TextView,里面添加图片和文字。TextView里面加图片简单,只要添加android:drawableTop="icon的ID",但是这有一个问题,在尝试用drable.setBounds设置图片的宽高的时候,无效。

这篇文章解决TextView drawableLeft左侧图片大小不可控的问题,提供了一个思路,即自定义一个ImageTextView控件,继承自TextView,重写基类的方法,实现设置往里添加图片的宽,高,位置等。我把java转换成kotlin,再加了一些注释。原程序有bug可以看原文下面的评论。原文还引入了一个DensityUtil的类,作用是实现px和pd的转换,我注释起来了

·在values下添加attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ImageTextView">
<attr name="drawable" format="reference"/>
<attr name="drawableWidth" format="dimension"/>
<attr name="drawableHeight" format="dimension"/>
<attr name="postion" format="integer"/>
</declare-styleable>
</resources>

·新建ImageTextView类

class ImageTextView: TextView {
private var mDrawable: Drawable?=null //资源id
private var mScaleWidth:Int?=null //设置图片宽
private var mScaleHeight:Int?=null //设置图片高
private var mPosition:Int?=null //设置图片位置 //初始化基类
constructor(context: Context):super(context){
}
constructor(context: Context, attrs: AttributeSet):super(context,attrs){
init(context,attrs);
}
constructor(context: Context, attrs: AttributeSet, defStyleAttr:Int):super(context, attrs, defStyleAttr){
init(context,attrs);
} //初始化获得xml文件中设置的属性
protected fun init(context: Context, attrs: AttributeSet){
//obtainStyledAttributes获得style中指定资源的属性值,返回typeArray
var typeArray=context.obtainStyledAttributes(attrs, R.styleable.ImageTextView) mDrawable=typeArray.getDrawable(R.styleable.ImageTextView_drawable)
println("在初始化中!!!!!!!")
println(mDrawable) //getDimensionPixelOffset获得指定资源id对应的尺寸,第二个参数,应该是表示如果没有这个属性则返回指定的值
//返回的是绝对尺寸单位,而非相对尺寸,即不是dp/sp。所以第二个参数就需要从dp转换成px,因此用到DensityUtil类
//mScaleHeight=typeArray.getDimensionPixelOffset(R.styleable.ImageTextView_drawableHeight,DensityUtil.dip2px(context,20f))
mScaleHeight=typeArray.getDimensionPixelOffset(R.styleable.ImageTextView_drawableHeight,20)
println("mScaleHeight的值得是多少$mScaleHeight")
mScaleWidth=typeArray.getDimensionPixelOffset(R.styleable.ImageTextView_drawableWidth,20)
println("mScaleWidth的值得是多少$mScaleWidth")
//第二个参数也是设置默认值
mPosition=typeArray.getInt(R.styleable.ImageTextView_postion,3) } //重写TextView的方法onMeasure,这个方法一般用来设置自定义view的尺寸。如果不重写此方法,那么默认尺寸与父控件相同
//
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
println("在设置尺寸中!!!!!!!")
println(mDrawable?.bounds)
//报错 smart cast to X is impossible,because X is a mutable property that could hava been changed by this time
//if(mDrawable!=null){...}
//mDrawable!!.setBounds(0,0,300,300) //DensityUtil.dip2px转换有问题
//mDrawable!!.setBounds(0,0,DensityUtil.dip2px(context,mScaleWidth!!.toFloat()),DensityUtil.dip2px(context,mScaleHeight!!.toFloat())) var scale=context.resources.displayMetrics.density;
var dpScaleHeight:Float=mScaleHeight!!*scale+0.5f
var dpScaleWidth:Float=mScaleWidth!!*scale+0.5f
mDrawable!!.setBounds(0,0,dpScaleWidth!!.toInt(),dpScaleHeight!!.toInt()) println(mDrawable?.bounds)
this.setCompoundDrawables(null,mDrawable,null,null) } // override fun onDraw(canvas: Canvas?) {
// super.onDraw(canvas)
// when(mPosition){
// 1->this.setCompoundDrawables(mDrawable,null,null,null)
// 2->this.setCompoundDrawables(null,mDrawable,null,null)
// 3->this.setCompoundDrawables(null,null,mDrawable,null)
// 4->this.setCompoundDrawables(null,null,null,mDrawable)
// }
// } // protected fun setDrawableLeft(drawable: Drawable){
// this.mDrawable=drawable;
// }
//
// protected fun setDrawableLeft(drawableRes:Int,context: Context){
// //
// var mDrawable= ContextCompat.getDrawable(context,R.drawable.ic_php)
// invalidate();
// } }

·在布局中使用ImageTextView

在根布局中添加:

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

(之后在ImageTextView中使用自定的属性要使用标签<app:> 而不是<android:>

添加布局代码,因为属于ViewPager的滑动页面,所以新建一个fragment_home_nav_program.xml吧

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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="wrap_content"
android:orientation="horizontal"> <com.vocus.justtest.view.ImageTextView
android:id="@+id/nav_item_php"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:drawableTop="@drawable/ic_nav_php"
android:gravity="center"
android:text="Php"
app:drawable="@drawable/ic_nav_php"
app:drawableHeight="25dp"
app:drawableWidth="25dp" /> <com.vocus.justtest.view.ImageTextView
android:id="@+id/nav_item_c"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:drawableTop="@drawable/ic_nav_c"
android:gravity="center"
android:text="C/c++"
app:drawable="@drawable/ic_nav_c"
app:drawableHeight="25dp"
app:drawableWidth="25dp" /> <com.vocus.justtest.view.ImageTextView
android:id="@+id/nav_item_java"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:drawableTop="@drawable/ic_nav_java"
android:gravity="center"
android:text="Java"
app:drawable="@drawable/ic_nav_java"
app:drawableHeight="25dp"
app:drawableWidth="25dp" /> <com.vocus.justtest.view.ImageTextView
android:id="@+id/nav_item_python"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:drawableTop="@drawable/ic_nav_python"
android:gravity="center"
android:text="Python"
app:drawable="@drawable/ic_nav_python"
app:drawableHeight="25dp"
app:drawableWidth="25dp" />
</LinearLayout>

·设置导航ViewPager的Adapter,新建HomePageNavAdapter类,这里先随便写一下

class HomePageNavAdapter : PagerAdapter() {
override fun instantiateItem(container: ViewGroup, position: Int): Any {
//return super.instantiateItem(container, position)
when (position) {
0 -> {
var navView = LayoutInflater.from(container.context)
.inflate(R.layout.fragment_home_nav_program, container, false)
container.addView(navView)
return navView
}
1 -> {
var textView = TextView(container.context)
textView.text = "第二页..."
container.addView(textView)
return textView
}
2 -> {
var textView = TextView(container.context)
textView.text = "第三页..."
container.addView(textView)
return textView
}
else ->{
var textView=TextView(container.context)
textView.text="超出范围了"
return textView
}
} } override fun isViewFromObject(view: View, `object`: Any): Boolean {
return view == `object`
} override fun getCount(): Int {
return 3
} override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
container.removeView(`object` as View)
} }

·实现一条导航线跟着Viewpager的页面移动而移动,这篇文章ViewPager 的顶部滑动线 提供了一个思路,就是在viewpager滚动监听中,改变移动线的leftMargin。

我在尝试使用文章提到的方法的时候,遇到了几个问题

·当使用homepage_nav_moveLine.layoutParams.leftMargin获取移动线的leftMargin时,提示

cannot inline bytecide built with JVM target 1.8 into bytecode that is being built with JVM target 1.6

需要在build.grandle中添加

compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
} kotlinOptions {
jvmTarget = "1.8"
}

·当给它赋值的时候,提示

val cannot be reassigned

查看.leftMargin方法在View类中的定义

inline val View.marginLeft: Int
get() = (layoutParams as? MarginLayoutParams)?.leftMargin ?: 0

所以只要改成

homepage_nav_moveLine.layoutParams as ViewGroup.MarginLayoutParams).leftMargin

另外,后面用到手机屏幕的尺寸信息,所以定义一个类ScreenDimen,并把它定义成单例模式。关于带参数的单例模式,参考

class ScreenDimen private constructor(context: Context){
private var context:Context?=null
private var windowManager:WindowManager?=null
private var displayMetrics:DisplayMetrics?=null init{
windowManager=(context.getSystemService(Context.WINDOW_SERVICE) as WindowManager)
displayMetrics= DisplayMetrics()
windowManager!!.defaultDisplay.getMetrics(displayMetrics)
} companion object{
var instance:ScreenDimen?=null fun getInstance(context: Context):ScreenDimen{
if(instance==null){
synchronized(ScreenDimen::class){
if(instance==null){
instance= ScreenDimen(context)
}
}
}
return instance!!
}
} //获得屏幕宽度pixel
fun getScreenWidthPix():Int{
return displayMetrics!!.widthPixels
} //获得宽度pixel
fun getScreenHeightPix():Int{
return displayMetrics!!.heightPixels
} //获取屏幕宽度dp
fun screenWidthDp():Int{
var screenWidthDp=getScreenWidthPix()/displayMetrics!!.density
return screenWidthDp.toInt()
} fun screenHeightDp():Int{
var screenHeightDp=getScreenHeightPix()/displayMetrics!!.density
return screenHeightDp.toInt()
} }

· 最后写成这样了

 //加载导航ViewPager
vp_homePageNav.adapter = HomePageNavAdapter() //define a move line
vp_homePageNav.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
private var currentPage: Int? = null
private var screenWidth = ScreenDimen.getInstance(context!!).getScreenWidthPix()//屏幕像素
private var moveLineDefaultLeftMargin = (homepage_nav_moveLine.layoutParams as ViewGroup.MarginLayoutParams).leftMargin override fun onPageScrollStateChanged(state: Int) {
} override fun onPageScrolled(
position: Int, //0,1,2
positionOffset: Float,
positionOffsetPixels: Int
) {
//有延时
var mvLeftMargin=0
var progLeftMargin=(screenWidth/3-homepage_nav_moveLine.width)/2
var docLetLeftMargin=screenWidth/3+(screenWidth/3-homepage_nav_moveLine.width)/2
var sourceLeftMargin=(screenWidth/3)*2+(screenWidth/3-homepage_nav_moveLine.width)/2 when (position) {
0 -> {
view!!.invalidate()
//homepage_nav_prog.typeface=Typeface.defaultFromStyle(Typeface.BOLD)
mvLeftMargin = progLeftMargin
mvLeftMargin += positionOffsetPixels / 3
(homepage_nav_moveLine.layoutParams as ViewGroup.MarginLayoutParams).leftMargin=mvLeftMargin
}
1 -> {
view!!.invalidate()
mvLeftMargin=docLetLeftMargin
mvLeftMargin += positionOffsetPixels / 3
(homepage_nav_moveLine.layoutParams as ViewGroup.MarginLayoutParams).leftMargin=mvLeftMargin
}
2 -> {
view!!.invalidate()
mvLeftMargin =sourceLeftMargin
mvLeftMargin += positionOffsetPixels / 3
(homepage_nav_moveLine.layoutParams as ViewGroup.MarginLayoutParams).leftMargin=mvLeftMargin
}
} }
override fun onPageSelected(position: Int) {
currentPage = position //保存当前所在页面
} }) //add new code }

效果不理想,滑动页面,线的移动会延时,而且没有平滑移动

有点乱,应该是我哪里写错了。但是有一种比调整leftMargin更简单的方法,就是使用动画

                var positionOffsetPd=ScreenDimen.getInstance(context!!).pixToPd(positionOffsetPixels)

                ViewCompat.animate(homepage_nav_moveLine).translationX(position*screenWidth/3+positionOffsetPd/3+((screenWidth/3-homepage_nav_moveLine.width))/2+10).setDuration(0)

在onPageScrollStateChanged中positionOffsetPixels是滑动页面的偏移像素,这里把它转换成dp值。positon是第几页

滑动一次页面,会产生一长串的positionOffsetPixels,系统也会多次调用onPageScrollStateChanged函数,每调用一次,就创建一个duration为0的动画(瞬间跳转,相当于平移)

·接着要实现滑动子类导航页面时,改变父类导航的字体为加粗

写在onPageSelected,因为这个函数每滑动翻页一次才被调用一次

override fun onPageSelected(position: Int) {
homepage_nav_prog.setTypeface(Typeface.defaultFromStyle(Typeface.NORMAL))
homepage_nav_doc.setTypeface(Typeface.defaultFromStyle(Typeface.NORMAL))
homepage_nav_source.setTypeface(Typeface.defaultFromStyle(Typeface.NORMAL))
when(position){
0->{
homepage_nav_prog.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD))
}
1->{
homepage_nav_doc.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD))
}
2->{
homepage_nav_source.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD))
}
}
}
·还有个,点击父类导航,应该把viewPager滑动到对应的子类导航页面,那就添加响应事件吧
但是有个问题,如果添加了无限翻页,代码写成这样
homepage_nav_prog.setOnClickListener{
vp_homePageNav.currentItem= Int.MAX_VALUE/2-Int.MAX_VALUE/2%3
}
homepage_nav_doc.setOnClickListener{
vp_homePageNav.currentItem= Int.MAX_VALUE/2-Int.MAX_VALUE/2%3+1
}
homepage_nav_source.setOnClickListener{
vp_homePageNav.currentItem= Int.MAX_VALUE/2-Int.MAX_VALUE/2%3+2
}
 
当翻了很多页的时候,点击按钮,与要跳转到的页面隔了很多页,那么就会很耗时,有人说可以重写setCurrentItem方法,但是我看kotlin把java的setCurrentItem封装了很多层。那想到一个办法
homepage_nav_prog.setOnClickListener{
if(vp_homePageNav.currentItem%3==1){
vp_homePageNav.currentItem-=1
}else if(vp_homePageNav.currentItem%3==2){
vp_homePageNav.currentItem-=2
} }
homepage_nav_doc.setOnClickListener{
if(vp_homePageNav.currentItem%3==0){
vp_homePageNav.currentItem+=1
}else if(vp_homePageNav.currentItem%3==2){
vp_homePageNav.currentItem-=1
}
}
homepage_nav_source.setOnClickListener{
if(vp_homePageNav.currentItem%3==0){
vp_homePageNav.currentItem+=2
}else if(vp_homePageNav.currentItem%3==1){
vp_homePageNav.currentItem+=1
}
}
这样保证跳转是在三个页面之间进行的。再设置vp_homePageNav.offscreenPageLimit=3,表示默认会同时创建3个页面,那切换的时候,是在三个创建好的页面之间,问题就解决了

另外还有子类导航的点击事件没有设置,但是先就这样吧

源码

链接:https://pan.baidu.com/s/1cAkrnFyNhzPyObVX_SkMFw
提取码:qtpk

尝试用kotlin做一个app(二)的更多相关文章

  1. 尝试用kotlin做一个app(写在前面)

    学kotlin的目的好像就是做一个app,不一定有什么想做的项目,只是单纯想掌握这一门技术,确切地说只是单纯想学会做app.对于概念的东西,我也没兴趣深究,用得到的学一下,用不到的,就算了.我也不知道 ...

  2. 尝试用kotlin做一个app(五)

    JSP后台管理系统 开发工具是IntelliJ IDEA+tomcat+mysql5.6.19+mysql-connector-java-5.1.48.jar+easyui+kindeditor 之前 ...

  3. 尝试用kotlin做一个app(一)

    1.先添加一下anko库 依赖:implementation "org.jetbrains.anko:anko:$anko_version" 版本:ext.anko_version ...

  4. 尝试用kotlin做一个app(四)

    本来是应该为主页加载数据库数据了,但是想着做后台,之前写jsp后台写吐了,所以先拖几天.把之前的代码完善一下,或者添加些新内容. ...... 多个fragment切换卡顿 首先修正一个bug.从主页 ...

  5. 尝试用kotlin做一个app(三)

    新闻列表 添加新闻列表可以使用RecyclerView.但是有个问题,RecyclerView只会在内部滚动,不会带动整个屏幕滚动.所以在原根布局外层添加androidx.core.widget.Ne ...

  6. 涨姿势:创业做一个App需要花多少钱(8个人,6个月,就要100万,附笔记心得)

    (原标题:涨姿势:创业做一个App要花多少钱?) 作为互联网从业者,被外行的朋友们问及最多的问题是,“做一个网站需要多少钱?”或者“做一个APP需要多少钱?”. 作为做过完整网站项目和APP的人,今天 ...

  7. 创业成本?亲身经历告诉你做一个app要多少钱

    导语:作为一名苦逼的移动互联网创业者,被外行的朋友们问及最多的问题是“做一个网站需要多少钱?”或者“做一个APP需要多少钱?” 作为一名苦逼的移动互联网创业者,被外行的朋友们问及最多的问题是“做一个网 ...

  8. 做一个 App 前需要考虑的几件事

    做一个 App 前需要考虑的几件事  来源:limboy的博客   随着工具链的完善,语言的升级以及各种优质教程的涌现,做一个 App 的成本也越来越低了.尽管如此,有些事情最好前期就做起来,避免当 ...

  9. 做一个App前需要考虑的几件事

    本文转载于文章原文链接,版本归原作者所有! 随着工具链的完善,语言的升级以及各种优质教程的涌现,做一个 App 的成本也越来越低了.尽管如此,有些事情最好前期就做起来,避免当 App 有了一定规模后, ...

随机推荐

  1. USACO[19-20]Dec银组题解

    1,MooBuzz 这题其实是道数学题. 我们先找找符合要求的数:1,2,4,7,8,11,13,14…… 我们发现再往后找都是这8个数中的一个加15k如:16……19……29…… 找规律发现k=n/ ...

  2. vue 路由过渡效果(1)

    1.html界面 <transition name="slide"> <router-view></router-view> </tran ...

  3. shiro缓存配置

    realm的缓存 方法一: 在securityManager配置中添加cacheManager配置项,会注入到realm中. 方法二:在realm中配置. realm本身实现了CacheManager ...

  4. HihoCoder第三周与POJ2406:KMP算法总结

    HihoCoder第三周: 输入 第一行一个整数N,表示测试数据组数. 接下来的N*2行,每两行表示一个测试数据.在每一个测试数据中,第一行为模式串,由不超过10^4个大写字母组成,第二行为原串,由不 ...

  5. RAID与磁盘管理之——综合应用

    为了实现磁盘的管理和RAID的综合,现将四块硬盘组合成一个RAID10,并在此基础之上创建物理卷.卷组.逻辑卷,实现在线扩容,最后挂载使用. 其中也部分包含了swap分区的创建和使用. 1.根据lin ...

  6. 05.swoole学习笔记--定时器

    <?php //循环执行的定时器 swoole_timer_tick(,function($timer_id){ echo "执行 $timer_id \n"; }); sw ...

  7. Redis详解(八)——企业级解决方案

    Redis详解(八)--企业级解决方案 缓存预热 缓存预热就是系统上线后,提前将相关的缓存数据直接加载到缓存系统.避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓 ...

  8. Windows下使用Tomcat

    tomcat简介 Tomcat是一个开源.免费.轻量级的web服务器,只支持部分JavaEE规范(Servlet.JSP),适合部署中小型.并发访问量不大的web项目,是部署中小型Java Web项目 ...

  9. C++ 语言程序设计(清华大学)2

    面向对象 1.面向对象程序基本特点:抽象.封装.继承.多态 2.类分为:公有类型成员public(接口),私有类型成员private(本类函数访问,类外要用友元函数访问),保护类型成员protect( ...

  10. Day 27:Xpath技术

    xPath技术 问题:当使用dom4j查询比较深的层次结构的节点(标签,属性,文本),比较麻烦!!! xPath作用 主要是用于快速获取所需的节点对象. 在dom4j中如何使用xPath技术 1.导入 ...