精华内容
下载资源
问答
  • 自定义ViewPager,设置ViewPager的背景图片,当ViewPager切换页面时,背景图片自动滚动,根据计算,当ViewPager切换到最后一个页面时,背景也同时滑动到最后,如果背景图片宽度较小,ViewPager每次切换时,背景图片...
  • 其实我这个就是实现四个点的效果,通过滑动viewpager来选中当前的点进行变色类似这种效果图,颜色可以自己修改还有大小不多说直接上代码自定义属性xml<?xml version="1.0" encoding="utf-8&...

    其实我这个就是实现四个点的效果,通过滑动viewpager来选中当前的点进行变色

    类似这种效果图,颜色可以自己修改还有大小

    不多说直接上代码

    自定义属性xml

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <declare-styleable name="PagePointView">
            <attr name="circleInterval" format="dimension"/>
            <attr name="circleSizi" format="dimension"/>
            <attr name="circleNumber" format="integer"/>
            <attr name="selectIndex" format="integer"/>
            <attr name="circleSelectColor" format="color"/>
            <attr name="circleUnSelectColor" format="color"/>
        </declare-styleable>
    </resources>

    代码

    public class PagePointView extends View {
        private Paint mPaint;
        private int mCircleInterval;//设置圆和点圆之间的间隔
        private int mCircleRadius;//设置圆的大小
        private int mCircleNumber;//设置圆的个数
        private int mSelectIndex = 1;//设置选中的圆
        private int mScreenWidht;
        private int mScreenHeight;
        private int mWidht;
        private int mHeight;
        private int mSelectColor;
        private int mUnSelectColor;
    
    
        public void setmCircleNumber(int mCircleNumber) {
            this.mCircleNumber = mCircleNumber;
        }
    
        public void setIndex(int selectIndex) {
            if (selectIndex > mCircleNumber) {
                this.mSelectIndex = mCircleNumber;
            } else {
                this.mSelectIndex = selectIndex;
            }
            postInvalidate();
        }
    
        public void setmSelectColor(int mSelectColor) {
            this.mSelectColor = mSelectColor;
        }
    
        public void setmUnSelectColor(int mUnSelectColor) {
            this.mUnSelectColor = mUnSelectColor;
        }
    
        public PagePointView(Context context) {
            this(context, null);
        }
    
        public PagePointView(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public PagePointView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            mPaint = new Paint();
            mPaint.setAntiAlias(true);
            mPaint.setColor(Color.BLACK);
            mPaint.setStrokeWidth(20);
            mPaint.setStyle(Paint.Style.FILL);
            mScreenWidht = context.getResources().getDisplayMetrics().widthPixels;
            mScreenHeight = context.getResources().getDisplayMetrics().heightPixels;
    
            TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PagePointView, defStyleAttr, 0);
            int count = ta.getIndexCount();
            for (int i = 0; i < count; i++) {
                int index = ta.getIndex(i);
                switch (index) {
                    case R.styleable.PagePointView_circleInterval:
                        mCircleInterval = ta.getDimensionPixelSize(index, (int) TypedValue.applyDimension
                                (TypedValue.COMPLEX_UNIT_PX, 12, context.getResources().getDisplayMetrics()));
                        break;
                    case R.styleable.PagePointView_circleNumber:
                        mCircleNumber = ta.getInt(index, 4);
                        break;
                    case R.styleable.PagePointView_circleSizi:
                        mCircleRadius = ta.getDimensionPixelOffset(index, (int) TypedValue.applyDimension
                                (TypedValue.COMPLEX_UNIT_DIP, 12, context.getResources().getDisplayMetrics()));
                        break;
                    case R.styleable.PagePointView_selectIndex:
                        mSelectIndex = ta.getInt(index, 1);
                        break;
                    case R.styleable.PagePointView_circleSelectColor:
                        mSelectColor = ta.getColor(index, Color.WHITE);
                        break;
                    case R.styleable.PagePointView_circleUnSelectColor:
                        mUnSelectColor = ta.getColor(index, Color.BLACK);
                        break;
                }
            }
            ta.recycle();
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            int wMode = MeasureSpec.getMode(widthMeasureSpec);
            int wSize = MeasureSpec.getSize(widthMeasureSpec);
            int hMode = MeasureSpec.getMode(heightMeasureSpec);
            int hSize = MeasureSpec.getSize(heightMeasureSpec);
            int viewWidht = 0;
            int viewHeight = 0;
            switch (wMode) {
                case MeasureSpec.EXACTLY:
                    viewWidht = wSize;
                    break;
                case MeasureSpec.AT_MOST:
                    viewWidht = mCircleInterval + getPaddingLeft() + getPaddingRight() + mCircleRadius * 2;
                    break;
                case MeasureSpec.UNSPECIFIED:
                    viewWidht = wSize;
                    break;
            }
            switch (hMode) {
                case MeasureSpec.EXACTLY:
                    viewHeight = wSize;
                    break;
                case MeasureSpec.AT_MOST:
                    viewHeight = getPaddingBottom() + getPaddingTop() +  mCircleRadius * 2;
                    break;
                case MeasureSpec.UNSPECIFIED:
                    viewHeight = wSize;
                    break;
            }
            setMeasuredDimension(resolveSize(viewWidht, widthMeasureSpec), resolveSize(viewHeight, heightMeasureSpec));
        }
    
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
            mWidht = w;
            mHeight = h;
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            int mViewWidht;
            for (int i = 1; i <= mCircleNumber; i++) {
                if (mSelectIndex == i) {
                    mPaint.setColor(mSelectColor);
                    canvas.drawCircle((2 * i - 1) * mCircleRadius + mCircleInterval * (i - 1), mCircleRadius, mCircleRadius, mPaint);
                    continue;
                }
                mPaint.setColor(mUnSelectColor);
                canvas.drawCircle((2 * i - 1) * mCircleRadius + mCircleInterval * (i - 1), mCircleRadius, mCircleRadius, mPaint);
            }
        }
    
    
    }

    使用注意:必须在使用的布局xml文件的最外层布局中加上chipChildren=“false”否则只会显示选中的点

    使用在viewpager的监听中设置选中的点位置

    private class PageChangeListener implements ViewPager.OnPageChangeListener {
            @Override
            public void onPageScrollStateChanged(int position) {
    
            }
    
            @Override
            public void onPageScrolled(int position, float arg1, int arg2) {
    
            }
    
            @Override
            public void onPageSelected(int position) {
                // 设置底部小点选中状态
                pagePoint.setIndex(position+1);
            }
    
        }
    可以将上面代码封装到自定义PagePointView中这里没有做过多操作,赖,我的原则能用就行封装后面在做


    展开全文
  • 1. 声明式compose ui在简单页面可以通过隐藏,显示来实现页面切换。 简单的if else 举例: @Composable fun App() { val context = LocalContext.current var isOnline by remember { mutableStateOf...

    1. 声明式compose ui在简单页面可以通过隐藏,显示来实现页面切换。

    简单的if else 举例:

    @Composable
    fun App() {
        val context = LocalContext.current
        var isOnline by remember { mutableStateOf(checkIfOnline(context)) }
    
        if (isOnline) {
            Home()
        } else {
            OfflineDialog { isOnline = checkIfOnline(context) }
        }
    }
    

    2. "androidx.navigation:navigation-compose"导航库使用方式

    首先定义一个Compose组件,然后给组件一个导航状态。

    @Composable
    fun navController() {
        val navController = rememberNavController()
        val context = LocalContext.current
    }
    

    然后建立路由表

    sealed class NavScreen(val route: String) {
        object AScreen : NavScreen("AScreen ")
    
        object BScreen: NavScreen("BScreen")
    
        object CScreen: NavScreen("CScreen")
    }
    

    建立导航关系

    @Composable
    fun navController() {
        val navController = rememberNavController()
        val context = LocalContext.current
    	//指定起始页面 startDestination = NavScreen.AScreen.route
    	NavHost(navController = navController, startDestination = NavScreen.AScreen.route) {
            composable(NavScreen.AScreen .route) { backStackEntry ->
            	//从堆返回一个hilt ViewModel
                val viewModel = hiltNavGraphViewModel<MainViewModel>(backStackEntry = backStackEntry)
                AScreen (
                    mainViewModel = viewModel,
                    selectScreen = {
                        navController.navigate(it)
                    }
                )
    
            }
            composable(
                route = NavScreen.BScreen .route,
                arguments = listOf(navArgument("动画") { type = NavType.LongType })
            ) { backStackEntry ->
                val viewModel = hiltNavGraphViewModel<MainViewModel>(backStackEntry = backStackEntry)
                BScreen (
                    mainViewModel = viewModel,
                    pressOnBack = {
                    	//返回前一个页面
                        navController.popBackStack(navController.graph.startDestination, false)
                    })
            }
            composable(NavScreen.CScreen.route) { backStackEntry ->
                val viewModel = hiltNavGraphViewModel<MainViewModel>(backStackEntry = backStackEntry)
                CScreen(
                    mainViewModel = viewModel,
                    pressOnBack = {
                        navController.popBackStack(navController.graph.startDestination, false)
                    })
            }
        }
    }
    

    使用

    onClick = { selectScreen("CScreen") }
    

    优点:可以不用再通过反射拦截StartActivity去启动没有注册的Activity!

    缺点: 做为单Activity,没有用navigation可视化页面托动Fragment方便!

    3. 通过StartActivity导航

    这个就不介绍了。


    6月9日更新文章:

    1.0.0-bate06 升级 1.0.0-bate07 需要修改navigation-compose版本
    根据官网描述

    implementation "androidx.navigation:navigation-compose:1.0.0-alpha10"
    

    改为

    implementation "androidx.navigation:navigation-compose:2.4.0-alpha01"
    

    2.4.0-alpha01 弃用了上面的navController.graph.startDestination 改为 navController.graph.startDestinationId


    7月10日更新

    单Activity如何导航

    对if else方式的一种补充,可以利用Crossfade配合when,状态通过单Activity的viewModel管理,简单示例:
    MainViewModel.kt

    @HiltViewModel
    class MainViewModel @Inject constructor(
        private val dbRepository: DbRepository
    ) : BaseViewModel() {
    
        private val _selectedTab: MutableState<Int> = mutableStateOf(R.string.nav_a)
        val selectedTab: State<Int> get() = _selectedTab
    
        @MainThread
        fun selectTab(@StringRes tab: Int) {
            _selectedTab.value = tab
        }
    }
    

    然后在导航页面,比如:
    Choose.kt

    @Composable
    fun ChooseScreen(
        viewModel: MainViewModel
    ) {
        val selectedTab = NavScreen.getNavScreen(viewModel.selectedTab.value)
        Crossfade(targetState = selectedTab) { destination ->
            when (destination) {
                NavScreen.A -> { AScreen() }
                NavScreen.B-> { BScreen() }
                NavScreen.C-> { CScreen(viewModel) }
                NavScreen.D-> {DScreen(mainViewModel = viewModel)}
            }
        }
    }
    
    enum class NavScreen(
        @StringRes val route: Int
    ) {
        A(R.string.nav_a),
        B(R.string.nav_b),
        C(R.string.nav_c,
        D(R.string.nav_d);
    
        companion object {
            fun getNavScreen(@StringRes route: Int): NavScreen {
                for (e in values()) {
                    if (e.route == route) {
                        return e
                    }
                }
                return A
            }
        }
    }
    

    需要导航时,我们只要在任何onClick方法中改变viewModel中selectedTab(所以上面代码是用=,不用remember)

    onClick= { mainViewModel.selectTab(R.string.A) }
    

    这里引申一下,就可以实现我们自定义的ViewPager

    自定义ViewPager

    因为需要跨项目引用,我把通用Compose组件模块化了,所以有一个接口。不用密闭类接口是因为不能跨包访问,最后选择枚举+接口封装。
    ITabPage.kt

    interface ITabPage {
        val title: Int
        val icon: Int
        val backColor: Int
        val serial: Int
    }
    

    ViewPager.kt

    @Composable
    fun ViewPager(
        backgroundColor: Color,
        settingTabPage: ITabPage,
        tabPagesList: List<ITabPage>,
        onTabSelected: (settingTabPage: ITabPage) -> Unit
    ) {
        TabRow(
        	// 切换动画是仿造google AnimationCodeLab
            //google在这使用ordinal 是不规范的,Enum规范中说明,ordinal是给EnumSet和EnumMap使用
            selectedTabIndex = settingTabPage.serial,
            backgroundColor = backgroundColor,
            indicator = { tabPositions ->
                HomeTabIndicator(tabPositions, settingTabPage)
            }
        ) {
    
            tabPagesList.forEach { tab ->
                TabItem(icon = painterResource(tab.icon), title = stringResource(tab.title), onClick = {
                    onTabSelected(tab)
                })
            }
    
        }
    }
    
    /**
     * 显示选项卡的指示器。
     */
    @Composable
    private fun HomeTabIndicator(
        tabPositions: List<TabPosition>,
        settingTabPage: ITabPage
    ) {
        val transition = updateTransition(
            settingTabPage,
            label = "Tab indicator"
        )
        val indicatorLeft by transition.animateDp(
            transitionSpec = {
                if (this.initialState.serial < this.targetState.serial){
                    spring(stiffness = Spring.StiffnessVeryLow)
                }else{
                    spring(stiffness = Spring.StiffnessLow)
                }
            },
            label = "Indicator left"
        ) { page ->
            tabPositions[page.serial].left
        }
        val indicatorRight by transition.animateDp(
            transitionSpec = {
                if (this.initialState.serial < this.targetState.serial){
                    spring(stiffness = Spring.StiffnessLow)
                }else{
                    spring(stiffness = Spring.StiffnessVeryLow)
                }
            },
            label = "Indicator right"
        ) { page ->
            tabPositions[page.serial].right
        }
    
        val color by transition.animateColor(
            label = "Border color"
        ) { page ->
            colorResource(id = page.backColor)
        }
        Box(
            Modifier
                .fillMaxSize()
                .wrapContentSize(align = Alignment.BottomStart)
                .offset(x = indicatorLeft)
                .width(indicatorRight - indicatorLeft)
                .padding(4.dp)
                .fillMaxSize()
                .background(color = color.copy(alpha = 0.3f))
            /*.border(
                BorderStroke(2.dp, color),
                RoundedCornerShape(4.dp)
            )*/
            //需求说要切换底色不要框,要框可以放开注释,过着增加入参封装
        )
    }
    
    /**
     * 显示一个标签。
     */
    @Composable
    private fun TabItem(
        icon: Painter,
        title: String,
        onClick: () -> Unit,
        modifier: Modifier = Modifier
    ) {
        Row(
            modifier = modifier
                .clickable(onClick = onClick)
                .padding(16.dp),
            horizontalArrangement = Arrangement.Center,
            verticalAlignment = Alignment.CenterVertically
        ) {
            Image(
                painter = icon,
                modifier = Modifier.size(30.dp),
                contentDescription = null
            )
            Spacer(modifier = Modifier.width(16.dp))
            Text(text = title)
        }
    }
    

    使用示例(修正,ViewPager和Crossfade应该被Cloum或Scaffold布局):

    @Composable
    fun ConfigScreen(){
    
        var tabPage: ITabPage by remember { mutableStateOf(TabPage.INFORMATION) }
        val tabPages : List<ITabPage> by remember{ mutableStateOf(TabPage.values().toList()) }
    
        ViewPager(
            backgroundColor = MaterialTheme.colors.background,
            settingTabPage = tabPage,
            tabPagesList = tabPages) { tabPage = it }
    
        Crossfade(targetState = tabPage) { destination ->
            when(destination) {
                TabPage.INFORMATION -> { InformationConfigPage() }
                TabPage.OPERATOR -> { OperatorConfigPage() }
                TabPage.SPECIMEN -> { SpecimenConfigPage() }
            }
        }
    }
    
    internal enum class TabPage(
        @StringRes override val title: Int,
        @DrawableRes override val icon: Int,
        override val serial: Int,
        @ColorRes override val backColor: Int
    ) : ITabPage{
        INFORMATION(R.string.tabpage_information,R.drawable.information_config,0,R.color.primaryDarkColor),
        OPERATOR(R.string.tabpage_operator,R.drawable.config,1,R.color.primaryDarkColor),
        SPECIMEN(R.string.tabpage_specimen,R.drawable.specimen_config,2,R.color.primaryDarkColor);
    }
    

    Crossfade不封装进ViewPager是因为在外层配置导航比较通用,不过可以把导航信息配置在枚举类中,这样封装可能更彻底。


    7月27日更新 :

    androidx.hilt:hilt-navigation-compose版本升级到 1.0.0-alpha03
    hiltNavGraphViewModel 弃用,用法更新为:

    	 val viewModel = hiltViewModel<MainViewModel>()
    

    或者

    //推荐
    composable(NavScreen.DataManager.route) { backStackEntry ->
    	 val viewModel = hiltViewModel<MainViewModel>(backStackEntry)
    }
    

    个人经验和一些看法。compose ui 非常适合单Activity应用,切换页面时可以做到基于view级别。这样之后MVVM架构理解起来会有些困难。单一个viewModel会导致高度耦合,其实是理解viewModel的偏差,个人理解viewModel是view的model,应该是与view绑定,至于实际使用总是绑定Activity是因为Activity有一个ViewGroup。还有一个是Lifecycle的误导。我用profiler测试val viewModel = hiltViewModel(backStackEntry)这种方式,内存中是会回收实例的。单Activity如果app不复杂的情况下,可以不使用导航,用前面Crossfade,然后viewmodel可以这样:

    @AndroidEntryPoint
    class MainActivity() : ComponentActivity() {
    
        @VisibleForTesting
        val viewModel: MainViewModel by viewModels()
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContent {
                LocalMainViewModel = compositionLocalOf { viewModel }
                TemplateTheme {
                    Surface(color = MaterialTheme.colors.background) {
                        Screen()
                    }
                }
            }
        }
    }
    

    之后在任何的screen中就可以获取viewmodel

    val viewModel = LocalMainViewModel.current
    
    展开全文
  • 原创的Android封装自定义ViewPager实现左右自动切换和手势滑动效果,不足之处请大家多多指出。
  • 自定义ViewPager实现Banner,知识点包含按下或者滑动的过程中停止轮播
  • 主要手把手教你用ViewPager自定义实现Banner轮播,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • 主要为大家详细介绍了Android自定义ViewPager实现纵向滑动翻页效果,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • Android自定义控件实现ViewPagerIndicator
  • 这是比较完美的viewPager自动手动循环滑动(常见于广告图轮询),不会有倒着循环缺陷,始终顺序循环,自定义ViewPager实现自动循环手动滑动翻页
  • 自定义ViewPager,可以实现页面之前的相互切换,可以自定义设置页面滚动的速度,一次滑动的事件间隔。同时该ViewPager解决了传统ViewPager和ListView,ScrollView滑动冲突问题,当然ListView的滑动冲突实在...
  • 一个自定义控件,实现ViewPager中拖动功能,并自定义相关控制事件
  • 自定义 viewpager

    2017-11-22 10:34:25
    自定义viewpager实现左右滑动的引导页,自定义viewpager实现左右滑动的引导页
  • 自定义viewpager

    2017-11-21 14:55:47
    自定义viewpager,实现页面的切换,扩展了原生android自带的viewpager
  • 在项目开发中,有时候需要用到Viewpager,却不需要它的左右滑动,这个时候可以自定义Viewpager,通过对它的事件分发方法返回值的修改实现Viewpager左右滑动的禁止.具体代码如下: public class NoScrollViewPager ...

    需求

    在项目开发中,有时候需要用到Viewpager,却不需要它的左右滑动,这个时候可以自定义Viewpager,通过对它的事件分发方法返回值的修改实现对Viewpager左右滑动的禁止.具体代码如下:

    public class NoScrollViewPager extends ViewPager {
        private boolean isScroll = false;
    
        public NoScrollViewPager(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public NoScrollViewPager(Context context) {
            super(context);
        }
    
        /**
         * 1.dispatchTouchEvent一般情况不做处理
         * ,如果修改了默认的返回值,子孩子都无法收到事件
         */
        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            return super.dispatchTouchEvent(ev);   // return true;不行
        }
    
        /**
         * 是否拦截
         * 拦截:会走到自己的onTouchEvent方法里面来
         * 不拦截:事件传递给子孩子
         */
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            // return false;//可行,不拦截事件,
            // return true;//不行,孩子无法处理事件
            //return super.onInterceptTouchEvent(ev);//不行,会有细微移动
            if (isScroll) {
                return super.onInterceptTouchEvent(ev);
            } else {
                return false;
            }
        }
    
        /**
         * 是否消费事件
         * 消费:事件就结束
         * 不消费:往父控件传
         */
        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            //return false;// 可行,不消费,传给父控件
            //return true;// 可行,消费,拦截事件
            //super.onTouchEvent(ev); //不行,
            //虽然onInterceptTouchEvent中拦截了,
            //但是如果viewpage里面子控件不是viewgroup,还是会调用这个方法.
            if (isScroll) {
                return super.onTouchEvent(ev);
            } else {
                return true;// 可行,消费,拦截事件
            }
        }
    
        public void setScroll(boolean scroll) {
            isScroll = scroll;
        }
    }
    

    在代码中特意增加了 boolean类型的 noScroll,可以控制是否禁止左右滑动,

    展开全文

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 17,528
精华内容 7,011
关键字:

自定义实现viewpager