尝试用kotlin做一个app(二)
导航条
我想实现的效果是这样的

类似于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
//这里是写错了的,setBounds传入的图片的宽,高值,不需要再转化成dp值了
//正确的应该是mDrawable!!.setBounds(0,0,mScaleWidth!!.toInt(),mScaleHeight!!.toInt())
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的页面移动而移动
通过改变leftMargin值实现线的移动➊。我试了效果不太好,而且略显复杂,所以我把它贴在文章最后了。
另一种,简便的方法是实用动画:设置addOnPageChangeListener监听,在onPageScrolled中加入:
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的动画(瞬间跳转,相当于平移)此处疑似有误,ViewCompat.animate(homepage_nav_moveLine).translationX后面接的应该是像素值,所以计算位移直接实用positionOffsetPixels即可,而无需转换为dp值,正确的应该是:
override fun onPageScrolled( position: Int, positionOffset: Float, positionOffsetPixels: Int ) { ViewCompat.animate(move_line) .translationX(position%3*(screenWidth/3)+positionOffsetPixels/3+(screenWidth/3-move_line.width)/2+0f) .setDuration(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)) } } }
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 }
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 } }
另外还有子类导航的点击事件没有设置,但是先就这样吧
源码
链接:https://pan.baidu.com/s/1cAkrnFyNhzPyObVX_SkMFw
提取码:qtpk
https://files.cnblogs.com/files/vocus/kotlin_app_2.zip
其他
➊通过改变leftMargin值实现线的移动
这篇文章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
}
效果不理想,滑动页面,线的移动会延时,而且没有平滑移动
有点乱,应该是我哪里写错了。
浙公网安备 33010602011771号